﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.Components
{
	#region CLASS GridDataRowCls : Správce dat jednoho řádku. Trvalá data o řádku, data elementů vstupní, data elementů vizuální, i filtrovaná časově.
	/// <summary>
	/// GridDataRowCls : Správce dat jednoho řádku. Trvalá data o řádku, data elementů vstupní, data elementů vizuální, i filtrovaná časově.
	/// </summary>
	internal class GridDataRowCls
	{
		#region KONSTRUKCE
		private GridDataRowCls(IDataRow rowItem, GridDataCls parent)
		{
			this.RowItem = rowItem;
			this.Parent = parent;
			this._ElementDict = new Dictionary<GID, GridDataElementCls>();
			this._ElementAllList = null;
			this._SetDefaultProperty();
		}
		/// <summary>
		/// Nastaví defaultní hodnoty do property
		/// </summary>
		private void _SetDefaultProperty()
		{
			this.ChartElementLoadedTimeRange = TimeRange.Empty;
		}
		/// <summary>
		/// Statický konstruktor
		/// </summary>
		/// <param name="rowItem"></param>
		/// <param name="parent"></param>
		/// <returns></returns>
		internal static GridDataRowCls Create(IDataRow rowItem, GridDataCls parent)
		{
			GridDataRowCls dataRow = new GridDataRowCls(rowItem, parent);
			dataRow.ChartElementsAdd(rowItem.ElementList, rowItem.AllElementsLoaded, rowItem.ElementLoadedTimeRange);
			return dataRow;
		}
		/// <summary>
		/// Soupis elementů, vytvořených ze vstupních dat.
		/// Každý unikátní GID je zde jen jednou.
		/// </summary>
		private Dictionary<GID, GridDataElementCls> _ElementDict { get; set; }
		/// <summary>
		/// Soupis všech položek, vytvořený ze vstupních dat, setříděný dle data počátku a umístěný na relativní pozice Top, Bottom
		/// </summary>
		private List<GridDataElementCls> _ElementAllList;
		/// <summary>
		/// Soupis aktuálně platných souřadnic jednotlivých linek výroby (oddělují celočíselné linky)
		/// </summary>
		private List<int> _UnitLineList;
        /// <summary>
        /// Soupis aktuálně viditelných položek, s ohledem na viditelné období a rozměr.
        /// Soupis je členěn podle vrstvy elementu (vrstva je klíčem do Dictionary, Value je pak seznam elementů této vrstvy).
        /// Urychluje se tím vykreslování, protože se postupně kreslí více vrstev, a nemusí se pro každou vrstvu filtrovat její elementy.
        /// </summary>
        private Dictionary<GraphLayerType, List<GridDataElementCls>> _ElementVisibleLayeredDict;
		#endregion
		#region PROPERTY
		/// <summary> { get; }
		/// Reference na Parent objekt = data grafu
		/// </summary>
		internal GridDataCls Parent { get; private set; }
		/// <summary> { get; }
		/// Data položky, získaná z datového zdroje
		/// </summary>
		internal IDataRow RowItem { get; private set; }
		/// <summary>
		/// Druh grafu, který je pro tento řádek zobrazen
		/// </summary>
		internal RowGraphMode RowGraph { get { return this.RowItem.RowGraph; } }
		/// <summary> { get; }
		/// GID tohoto záznamu = řádek.
		/// </summary>
		internal GID GId { get { return this.RowItem.GId; } }
        /// <summary>
        /// Jméno nodu. Mělo by být unikátní, zatím ale unikátnost není kontrolována. Používá se při persistenci pointerů na řádek(řádky).
        /// GID se může při příštím reloadu dat změnit, ale NodeName by nemělo.
        /// </summary>
        internal string NodeName { get { return this.RowItem.NodeName; } }
		/// <summary>
		/// Počet načtených elementů, bez dokalkulací = jen pro programovou vizualizaci.
		/// Ve formě počtu: "25/130" (viditelných / celkem).
		/// </summary>
		internal string ElementCount
		{
			get
			{
                string vis = (this._ElementVisibleLayeredDict == null ? "???" : this._ElementVisibleLayeredDict.Count.ToString());
				string all = (this._ElementAllList == null ? "???" : this._ElementAllList.Count.ToString());
				return vis + "/" + all;
			}
		}
		/// <summary> { get; }
		/// Soupis vizuálních elementů.
		/// Obsahuje tolik prvků, kolik je prvků ve slovníku DataItemDict.
		/// Vždy obsahuje korektně určené souřadnice Top a Bottom, řadí prvky odspodu (Bottom = 0) nahoru.
		/// Nejvyšší vypočtená pozice Top je uložena v this.TopMax.
		/// Upozornění: elementy, které jsou v tomto seznamu, nemusí být všechny viditelné.
		/// Všechny elementy mají korektně vypočtenou základní dimenzi (Rectangle), 
		/// ale neviditelné elementy nemusí mít vypočteny rozšiřující interní dimenze.
		/// Pokud se aplikace chce spolehnout na platnost všech dimenzí všech elementů, 
		/// nechť na elementu zavolá metodu GridDataElementCls.InternalDimensionCheck().
		/// Tato metoda zajistí kontrolu a v případě potřeby přepočet interních dimenzí.
		/// </summary>
		internal List<GridDataElementCls> ElementAllList
		{
			get
			{
				this._ElementAllListCheck();
				return this._ElementAllList;
			}
		}
		/// <summary> { get; }
		/// Soupis pozic Top, na nichž se mají kreslit vodorovné čáry, oddělující jednotlivé Lines.
		/// Souřadnice jsou v pixelech.
		/// </summary>
		internal List<int> CurrentLineList
		{
			get
			{
				this._UnitLineListCheck();
				return this._UnitLineList;
			}
		}
		/// <summary>
		/// Obsahuje (vrací) příznak, že soupis elementů this.ElementAllList je validní
		/// (obsahuje skutečně všechny elementy, a je určena jejich relativní pozice a je určena výška řádku).
		/// Pokud je tato property false, pak čtením jakýchkoli dat elementů dojde k jejich přepočtu a k možné změně výšky řádku (this.RowHeightPx).
		/// Naproti tomu čtení tohoto příznaku IsAllListValid nezpůsobí žádnou změnu.
		/// </summary>
		internal bool IsAllListValid { get { return !this._ElementAllListNeedPrepare; } }
		/// <summary>
		/// Obsahuje (vrací) příznak, že soupis elementů this.ElementVisibleList je validní
		/// (všechny jeho elementy mají korektní pozici, řádek má korektně určenou výšku).
		/// Poznámka: čtením seznamu this.ElementVisibleList může dojít k přepočtu pozic elementů a ke změně výšky řádku.
		/// Stejně tak čtením dat konkrétního elementu.
		/// Naproti tomu čtení tohoto příznaku IsVisibleListValid nezpůsobí žádnou změnu.
		/// </summary>
		internal bool IsVisibleListValid { get { return !this._ElementVisibleListNeedPrepare; } }
		/// <summary> { get; }
		/// Soupis aktuálně viditelných vizuálních elementů.
        /// Pozor: tento soupis se vždy vytváří ze soupisů z jednotlivých vrstev (viz GetElementVisibleListForLayer()), takže to malou chvilku potrvá.
        /// Tento List se typicky NEPOUŽÍVÁ k vykreslování.
		/// Obsahuje jen ty položky, jejichž čas je alespoň zčásti uvnitř viditelného časového intervalu this.VisibleTimeRange
		/// </summary>
		internal List<GridDataElementCls> ElementVisibleList
		{
			get
			{
				this._CurrentVisualItemListCheck();
                List<GridDataElementCls> result = new List<GridDataElementCls>();
                foreach (List<GridDataElementCls> layerList in this._ElementVisibleLayeredDict.Values)
                {
                    if (layerList != null)
                        result.AddRange(layerList);
                }
                return result;
			}
		}
        /// <summary>
        /// Vrátí soupis aktuálně viditelných vizuálních elementů konkrétní vrstvy.
        /// Nevytváří jej z ElementVisibleList filtrováním, ale je předem vytvořen a indexován.
        /// Obsahuje jen ty položky, jejichž čas je alespoň zčásti uvnitř viditelného časového intervalu this.VisibleTimeRange, v dané vrstvě.
        /// Pokud pro danou vrstvu neexistuje žádný element, vrátí se null.
        /// </summary>
        internal List<GridDataElementCls> GetElementVisibleListForLayer(GraphLayerType layer)
        {
			// Z důvodu multithreadového zpracování si musím získat soupis elementů
			List<GridDataElementCls> layerList = this._GetElementVisibleListForLayerLocked(layer);
			return layerList;
        }
        /// <summary> { get; }
		/// Objekt, který obsahuje metody použitelné pro přepočty vizuálních a časových dat na časové ose (datum - pixel).
		/// Jde o referenci na objekt časové osy, která interně implementuje objekt časové kalkulačky i pro své účely.
		/// Poskytuje přístup k tomuto objektu přes interface ITimeAxisCalc, přes které lze přepočítávat souřadnice.
		/// </summary>
		internal ITimeAxisCalc TimeAxisCalc { get { return this.Parent.TimeAxisCalc; } }
        /// <summary> { get; }
        /// Nejmenší výška řádku.
        /// </summary>
        internal int RowHeightTotalMinPx { get { return _GetDim(this.RowItem.RowHeightTotalMinPx, this.Parent.RowHeightTotalMinPx); } }
        /// <summary> { get; }
		/// Nejvyšší výška řádku. Pokud by výpočet určil vyšší hodnotu, bude řádek zmenšen na tuto výšku, a přepočten.
		/// Pokud bude uvedena hodnota 0, nebude se výška řádku kontrolovat.
		/// Hodnoty menší a rovno 10 budou rovněž ignorovány, z praktických důvodů zobrazování.
		/// </summary>
		internal int RowHeightTotalMaxPx { get { return _GetDim(this.RowItem.RowHeightTotalMaxPx, this.Parent.RowHeightTotalMaxPx); } }
		/// <summary> { get; }
		/// Požadovaná výška jedné linky řádku.
		/// Pokud se data v řádku nevejdou do celkové výšky RowHeightTotalMaxPx, 
		/// bude výška linky zmenšena (a uložena do DataRow.LineHeightCurrPx)
		/// tak, aby výška řádku odpovídala RowHeightTotalMaxPx.
		/// Platná hodnota je 10 až 100 pixelů. Jiné hodnoty budou zarovnány do těchto mezí.
		/// </summary>
		internal int RowHeightLineSolidPx { get { return _GetDim(this.RowItem.RowHeightLineSolidPx, this.Parent.RowHeightLineSolidPx); } }
		/// <summary>
		/// Typická výška jednoho řádku, pokud jde o řádek typu RowGraphMode.MultiTask, 
		/// a na tomto řádku je detekováno podílové čerpání kapacity (UseRatio je menší než 1).
		/// Systém sám detekuje takové řádky, a vykresluje je v této výšce linky namísto výšky RowHeightOneLine.
		/// Jde opět o výšku pro jeden stroj. Pokud bude v rámci kapacitní jednotky použito více (N) strojů, bude výška řádku N× vyšší.
		/// Implicitní hodnota je 60 pixelů.
		/// Platná hodnota je 7 až 100 pixelů. Jiné hodnoty budou zarovnány do těchto mezí.
		/// </summary>
		public int RowHeightLineFragmentPx { get { return _GetDim(this.RowItem.RowHeightLineFragmentPx, this.Parent.RowHeightLineFragmentPx); } }
		/// <summary>
		/// Výška nejmenšího zlomku v grafu typu RowGraphMode.MultiTask, kdy jsou zobrazeny položky využívající podíl kapacity.
		/// Pokud například operace využije 1/10 kapacity (plochy) stroje, a výška linky (RowHeightOneFragmentedLine) bude 30 pixelů, 
		/// pak by výška využití této 1/10 kapacity byla 3 pixely. Což je téměř neviditelné.
		/// Proto je možno deklarovat výšku minimálního zlomku: pokud v takovém případě bude RowHeightMinFragment = 6,
		/// pak fragment využívající 1/10 kapacity bude vysoký 6 pixelů, a úměrně k tomu bude výška linky = 60 pixelů (namísto zadaných 30).
		/// Implicitní hodnota je 4 pixely.
		/// Platná hodnota je 2 až 25 pixelů. Jiné hodnoty budou zarovnány do těchto mezí.
		/// </summary>
		public int RowHeightMinFragmentPx { get { return _GetDim(this.RowItem.RowHeightMinFragmentPx, this.Parent.RowHeightMinFragmentPx); } }
		/// <summary> { get; }
		/// Přídavek k výšce řádku, vyjádřený relativně k hodnotě this.LineHeight (fyzická výška jedné linky).
		/// Defaultní hodnota je 1F.
		/// Výška řádku se určí jako výška nejvyšší položky, zaokrouhlená na celé linky (LineHeight), a přičte se tento přídavek.
		/// </summary>
		internal decimal RowHeightAddLineSolidRatio { get { return _GetDim(this.RowItem.RowHeightAddLineSolidRatio, this.Parent.RowHeightAddLineSolidRatio); } }
		/// <summary> { get; }
		/// Přídavek k výšce fragmentovaného řádku, vyjádřený relativně k hodnotě this.LineHeight (fyzická výška jedné linky).
		/// Defaultní hodnota je 1F.
		/// Výška řádku se určí jako výška nejvyšší položky, zaokrouhlená na celé linky (LineHeight), a přičte se tento přídavek.
		/// </summary>
		internal decimal RowHeightAddLineFragmentRatio { get { return _GetDim(this.RowItem.RowHeightAddLineFragmentRatio, this.Parent.RowHeightAddLineFragmentRatio); } }
		/// <summary> { get; set; }
		/// Externí požadavek na přepočet výšky a svislé pozice položek, a výšky řádku.
		/// Bude proveden při příštím čtení dat.
		/// Po provedení přepočtu bude nastaven na false.
		/// </summary>
		internal bool RequestCalcVertical { get; set; }
		/// <summary> { get; set; }
		/// Externí požadavek na přepočet pozic X a aktuální výběr elementů dle viditelného data.
		/// Bude proveden při příštím čtení dat.
		/// Po provedení přepočtu bude nastaven na false.
		/// </summary>
		internal bool RequestCalcHorizontal { get; set; }
		/// <summary>
		/// Příznak, že se liší hodnota RowHeightPx od RowHeightLastAcceptedPx = došlo ke změně výšky řádku od výšky posledně akceptované.
		/// </summary>
		internal bool IsRowHeightChanged { get { return (this.RowHeightLastAcceptedPx != this.RowHeightPx); } }
		/// <summary> { get; set; }
		/// Výška řádku, kterou posledně akceptovaly externí aplikace.
		/// Je zde k dispozici výlučně pro externí použití, řádek jako takový tuto hodnotu nenastavuje.
		/// Externí aplikace si sem může uložit hodnotu RowHeightPx, pro kterou naposledy vypočítala pozice Top řádku.
		/// Existuje zde property this.IsRowHeightChanged, která signalizuje změnu (this.RowHeightLastAcceptedPx != this.RowHeightPx).
		/// </summary>
		internal int RowHeightLastAcceptedPx { get; set; }
		/// <summary> { get; }
		/// Aktuálně viditelná výška řádku.
		/// Pozor, tato hodnota je spolehlivá. Čtení této hodnoty může způsobit přepočet dat na řádku, pokud v okamžiku čtení nejsou data platná.
		/// Výška řádku se typicky mění interně, po načtení dat a přepočtu pozic jednotlivých elementů. 
		/// Nastaví se sem vypočtená = požadovaná výška řádku.
		/// Výpočet: řádek musí zobrazit všechny svoje položky (všechny, nejen neviditelné), plus část řádku navíc (viz property AddLines).
		/// Položky jsou rozmístěny na výšku podle jejich hodnoty UseRatio, tak aby se nepřekrývaly, a co nejvíce seděly u spodního kraje řádku.
		/// </summary>
		internal int RowHeightPx
		{
			get
			{
				this._ElementAllListCheck();
				return this._RowHeightPx;
			}
		}
		/// <summary> { get; }
		/// Aktuálně viditelná výška řádku.
		/// Pozor: tato hodnota odráží posledně platnou hodnotu, ale pokud se od poslední aktualizace dat něco změnilo
		/// (přibyly nové položky, nebo jiné ubyly, nebo se změnily deklarované výšky řádku, atd), pak tato hodnota neodpovídá aktuálnímu stavu.
		/// </summary>
		internal int RowHeightUncalculated
		{
			get
			{
				return this._RowHeightPx;
			}
		}
		private int _RowHeightPx;
		/// <summary>
		/// Aktuálně použitá výška jedné linky. Běžně je rovna this.LineHeightPx.
		/// Pokud je ale definována maximální výška řádku this.RowHeightMaxPx,
		/// a aktuální stav dat by překročil výšku řádku this.RowHeightMaxPx,
		/// pak je výška this.LineHeightPx přepočtena tak aby odpovídala this.RowHeightMaxPx
		/// a úměrně k tomu je upravena i hodnota this.LineHeightCurrPx.
		/// </summary>
		internal int LineHeightCurrPx { get; private set; }
		/// <summary>
		/// Aktuální počet Lines, s přidáním AddLinesRatio. V poměru, ne v pixelech.
		/// </summary>
		internal decimal LineHeightCurrRatio { get; private set; }
		/// <summary>
		/// Největší hodnota souřadnice Top (v hodnotách UseRatio) ze všech vizuálních prvků v poli this.VisualItemList
		/// </summary>
		internal decimal UseRatioSumMax { get; private set; }
		/// <summary>
		/// Nejmenší hodnota UseRatio ze všech prvků v poli this.VisualItemList (může pomoct určit LineHeight)
		/// </summary>
		internal decimal UseRatioOneMin { get; private set; }
		/// <summary>
		/// true, pokud tento řádek obsahuje zlomkové elementy (jejichž relativní výška není rovna 1.00)
		/// Pro takové řádky je vhodné vykreslit vodorovné linky, které znázorňují jednotky (jeden stroj).
		/// </summary>
		internal bool ContainFragments { get; private set; }
		/// <summary>
		/// Příznak, že tento řádek obsahuje podílové využití kapacit (UseRatio je přinejmenším v jednom elementu menší než 1)
		/// </summary>
		internal bool ContainUseRatio { get { return (this.UseRatioOneMin > 0M && this.UseRatioOneMin < 1M); } }
        /// <summary>
        /// Vrátí dimenzi přednostně z prvního parametru (pokud není nula), anebo z druhého parametru.
        /// Typicky první je z řádku, druhá z grafu.
        /// </summary>
        /// <param name="dimRow"></param>
        /// <param name="dimGraph"></param>
        /// <returns></returns>
        private decimal _GetDim(decimal? dimRow, decimal dimGraph)
        {
            if (dimRow.HasValue) return dimRow.Value;
            return dimGraph;
        }
        /// <summary>
        /// Vrátí dimenzi přednostně z prvního parametru (pokud není nula), anebo z druhého parametru.
        /// Typicky první je z řádku, druhá z grafu.
        /// </summary>
        /// <param name="dimRow"></param>
        /// <param name="dimGraph"></param>
        /// <returns></returns>
        private int _GetDim(int? dimRow, int dimGraph)
        {
            if (dimRow.HasValue) return dimRow.Value;
            return dimGraph;
        }
        #endregion
		#region ELEMENTY : přidání, hledání, smazání, invalidace, detekce existence
		/// <summary>
		/// Přidá do sebe data elementů z dodaného seznamu.
		/// </summary>
		/// <param name="dataList">Seznam elementů</param>
        internal bool ChartElementsAdd(IEnumerable<IDataElement> dataList, bool allElementsLoaded, TimeRange elementLoadedTimeRange)
		{
			bool isAdded = this.ChartElementsAdd(dataList);
			if (allElementsLoaded && !this.AllElementsLoaded)
				this.AllElementsLoaded = allElementsLoaded;
            if (!elementLoadedTimeRange.IsEmpty)
                this.ChartElementLoadedTimeRange = this.ChartElementLoadedTimeRange + elementLoadedTimeRange;
			return isAdded;
		}
		/// <summary>
		/// Přidá do sebe data elementů z dodaného seznamu.
		/// </summary>
		/// <param name="dataList">Seznam elementů</param>
		internal bool ChartElementsAdd(IEnumerable<IDataElement> dataList)
		{
			bool isAdded = false;
            if (dataList != null)
            {
                lock (this._ElementDict)
                {
                    foreach (IDataElement data in dataList)
                    {
                        if (!this._ElementDict.ContainsKey(data.GId))
                        {
                            this._ElementDict.Add(data.GId, new GridDataElementCls(this, data));
                            isAdded = true;
                        }
                    }
                    if (isAdded)
                        this._InvalidateAll();
                }
            }
			return isAdded;
		}
		/// <summary>
		/// Přidá do sebe data daného elementu
		/// </summary>
		/// <param name="element">Element</param>
		internal bool ChartElementAdd(IDataElement element)
		{
			bool isAdded = false;
			lock (this._ElementDict)
			{
				if (!this._ElementDict.ContainsKey(element.GId))
				{
					this._ElementDict.Add(element.GId, new GridDataElementCls(this, element));
					isAdded = true;
				}
				if (isAdded)
					this._InvalidateAll();
			}
			return isAdded;
		}
		/// <summary>
		/// Přidá do sebe daný elementu.
		/// Používá se při přesunu elementu mezi dvěma řádky.
		/// </summary>
		/// <param name="elementData">Vizuální data elementu</param>
		internal bool ChartElementAdd(GridDataElementCls elementData)
		{
			bool isAdded = false;
			lock (this._ElementDict)
			{
				if (!this._ElementDict.ContainsKey(elementData.GId))
				{
					elementData.Parent = this;             // Vloží sebe jako parenta řádku
					this._ElementDict.Add(elementData.GId, elementData);
					isAdded = true;
				}
				if (isAdded)
					this._InvalidateAll();
			}
			return isAdded;
		}
		/// <summary>
		/// Zajistí invalidaci dat všech elementů.
		/// Vrátí true, když byly nějaké elementy skutečně odstraněny.
		/// </summary>
		/// <returns></returns>
		internal bool ChartElementsInvalidate()
		{
			int elementCount = this._ElementDict.Count;
			this.ChartElementClear();
			return (elementCount > 0);
		}
		/// <summary>
		/// Zajistí invalidaci časového úseku.
		/// Neprovádí výmazy elementů, 
		/// ale zajistí, že data tohoto řádku pro dané období budou 
		/// při příštím zobrazení daného času nově donačtena z datového zdroje.
		/// </summary>
		/// <param name="timeRange"></param>
		/// <returns></returns>
		internal bool TimeRangeInvalidate(TimeRange timeRange)
		{
			TimeRange loadedTimeRange = this.ChartElementLoadedTimeRange;   // Tento čas je v tomto řádku nyní načten
			
			// Z času Loaded odebereme vstupní čas, a to tak aby v loadedTimeRange zůstal zbytek (ten větší načtený):
			TimeRange timeLeft = loadedTimeRange.SplitUp(timeRange.Begin, TimeRange.TimeDirection.ToHistory);
			TimeRange timeRight = loadedTimeRange.SplitUp(timeRange.End, TimeRange.TimeDirection.ToFuture);
			bool leftEmpty = timeLeft.IsEmpty;
			bool rightEmpty = timeRight.IsEmpty;
			if (leftEmpty && rightEmpty)
				this.ChartElementLoadedTimeRange = TimeRange.Empty;
			else if (leftEmpty && !rightEmpty)
				this.ChartElementLoadedTimeRange = timeRight;
			else if (!leftEmpty && rightEmpty)
				this.ChartElementLoadedTimeRange = timeLeft;
			else if (!leftEmpty && !rightEmpty && timeLeft.Seconds >= timeRight.Seconds)
				this.ChartElementLoadedTimeRange = timeLeft;
			else if (!leftEmpty && !rightEmpty && timeLeft.Seconds < timeRight.Seconds)
				this.ChartElementLoadedTimeRange = timeRight;
			else
				this.ChartElementLoadedTimeRange = TimeRange.Empty;

			return (this.ChartElementLoadedTimeRange != loadedTimeRange);
		}
		/// <summary>
		/// Invaliduje svoje vizuální data.
		/// V případě potřeby (get na Listy) si je vytvoří znovu, aktuální.
		/// </summary>
		private void _InvalidateAll()
		{
			this._ElementAllList = null;
            this._ElementVisibleLayeredDict = null;
			this.RequestCalcVertical = true;
			this.RequestCalcHorizontal = true;
		}
		/// <summary>
		/// Informace o časovém rozsahu, pro který jsou načtena data v poli this.ChartElements.
		/// Při změně časového rozmezí se data načítají z podkladové datové vrstvy znovu.
		/// Hodnota Empty znamená, že data dosud nebyla načtena.
		/// </summary>
		internal TimeRange ChartElementLoadedTimeRange
        {
            get { return this._ChartElementLoadedTimeRange; }
            set { this._ChartElementLoadedTimeRange = value; }
        }
        private TimeRange _ChartElementLoadedTimeRange;
		/// <summary>
		/// Najde a vrátí element pro daný GID, anebo vrátí null pokud neexistuje.
		/// Tato metoda zajistí, že element bude obsahovat korektní souřadnice v rámci svého řádku, 
		/// a to za každé situace (i těsně po načtení, i pro neviditelný řádek, i pro element mimo časovou osu).
		/// Tím je zajištěno, že volající funkce může získat jakýkoli element a spolehnout se na jeho souřadnici.
		/// Rovněž je v elementu nastaveno, zda se nachází ve viditelné části časové osy.
		/// Také je zohledněno, zda řádek sám je/není vybraný filtrem.
		/// Jedna věc zde není zohledněna: zda se řádek nachází (vertikálně) ve viditelné části grafu.
		/// Vertikální souřadnice uvedené v elementu se vztahují k řádku (Top = 0 = horní okraj řádku).
		/// Fyzická souřadnice Y je dána pozicí řádku (nodu).
		/// Poznámka: jeden datový řádek může být zobrazen ve více vizuálních řádcích (nody).
		/// </summary>
		/// <param name="graphGId">GID hledaného elementu</param>
		/// <returns>Data elementu</returns>
		internal GridDataElementCls GetChartElement(GID graphGId)
		{
			return this.GetChartElement(graphGId, false);
		}
		/// <summary>
		/// Najde a vrátí element pro daný GID, anebo vrátí null pokud neexistuje.
		/// Tato metoda zajistí, že element bude obsahovat korektní souřadnice v rámci svého řádku, 
		/// a to za každé situace (i těsně po načtení, i pro neviditelný řádek, i pro element mimo časovou osu).
		/// Tím je zajištěno, že volající funkce může získat jakýkoli element a spolehnout se na jeho souřadnici.
		/// Rovněž je v elementu nastaveno, zda se nachází ve viditelné části časové osy.
		/// Také je zohledněno, zda řádek sám je/není vybraný filtrem.
		/// Jedna věc zde není zohledněna: zda se řádek nachází (vertikálně) ve viditelné části grafu.
		/// Vertikální souřadnice uvedené v elementu se vztahují k řádku (Top = 0 = horní okraj řádku).
		/// Fyzická souřadnice Y je dána pozicí řádku (nodu).
		/// Poznámka: jeden datový řádek může být zobrazen ve více vizuálních řádcích (nody).
		/// </summary>
		/// <param name="graphGId">GID hledaného elementu</param>
		/// <param name="skipDimensionCheck">
		/// Požadavek, aby se neprováděly validace souřadnic nalezeného elementu.
		/// V některých situacích potřebujeme jen najít element (anebo jen získat informaci o tom, že tu je),
		/// a pak by určování souřadnic bylo zdržující.
		/// </param>
		/// <returns>Data elementu</returns>
		internal GridDataElementCls GetChartElement(GID graphGId, bool skipDimensionCheck)
		{
			GridDataElementCls element;

			// Vyhledání elementu:
			if (!this._ElementDict.TryGetValue(graphGId, out element))
				return null;

			if (!skipDimensionCheck)
			{
				// Validace souřadnic elementu:
				this._CurrentVisualItemListCheck();  // Zajistí platnost dat v seznamu this._CurrentVisualItemList, a platnosti dimenzí každého elementu.

				// Specifické doplnění dimenzí u elementu, pro který dosud nebyly interní dimenze určeny:
				element.InternalDimensionCheck();
			}

			return element;
		}
		/// <summary>
		/// Zajistí odebrání daného elementu.
		/// Používá se při editaci, kdy element odeberu z jednoho řádku a přidím jej do jiného.
		/// Současně zajistí invalidaci času, pro který jsou určena vizuální data (pravděpodobně bude třeba přepočítat vizuální data znovu).
		/// </summary>
		/// <param name="elementGId">GID elementu, který chcem vyjmout</param>
		internal bool ChartElementsRemove(GID elementGId)
		{
			bool result = false;
			lock (this._ElementDict)
			{	// Zámek nám zajistí, že po dobu modifikace záznamů v this.ChartElements nebude probíhat vizuální kalkulace návazných údajů:
				if (this._ElementDict.ContainsKey(elementGId))
				{
					this._ElementDict.Remove(elementGId);
					result = true;
				}
				if (result)
					this._InvalidateAll();           // Invaliduji pracovní data. V případě potřeby se vytvoří nově.
			}
			return result;
		}
		/// <summary>
		/// Zajistí přidání dočasného elementu podle požadavku na načítání ELEMENTŮ do svého seznamu elementů.
		/// Dočasný element vizuálně reprezentuje časový úsek, pro který nyní probíhá načítání dat.
		/// Po dokončení reálného načtení dat bude tento dočasný element odebrán - viz metoda ChartElementsTempDel().
		/// </summary>
		/// <param name="request">Specifikace požadavku</param>
        internal void ChartElementsTempAdd(DataRequestReadElementsForRows request)
		{
			GridIDataElementCls element = GridIDataElementCls.CreateTempElement(request);
			element.TextNote = "Data pro tento čas se načítají.";
			this.ChartElementAdd(element);
		}
		/// <summary>
		/// Zajistí přidání dočasného elementu podle požadavku na načítání ŘÁDKŮ do svého seznamu elementů.
		/// Používá se při asynchronním požadavku na donačtení podřízených řádků, když v době zpracovávání požadavku
		/// se v grafu rozsvítí "temp" řádek (s textem "Probíhá načtení řádků"), a v prostoru grafu se nachází temp element (ten, který zde přidáváme).
		/// Tento element ale nereprezentuje nějaký konkrétní čas, ale je vždy zobrazen přes celý grafický sloupec.
		/// Po dokončení reálného načtení dat bude tento dočasný element odebrán - viz metoda ChartElementsTempDel(),
		/// anebo do něj bude vepsána informace o chybě (viz metoda this.ChartElementsTempError()).
		/// </summary>
		/// <param name="request">Specifikace požadavku</param>
		internal void ChartElementsTempAdd(DataRequestReadRows request)
		{
			GridIDataElementCls element = GridIDataElementCls.CreateTempElement(request);
			element.TextNote = "Data pro tento řádek se načítají.";
			this.ChartElementAdd(element);
		}
		/// <summary>
		/// Zajistí odebrání dočasného elementu, vytvořeného podle požadavku na načítání do svého seznamu elementů.
		/// Současně zajistí invalidaci času, pro který jsou určena vizuální data (pravděpodobně bude třeba přepočítat vizuální data znovu).
		/// </summary>
        /// <param name="requestGId"></param>
        internal bool ChartElementsTempDel(GID requestGId)
		{
			bool result = false;
            if (!requestGId.IsEmpty)
            {
                lock (this._ElementDict)
                {	// Zámek nám zajistí, že po dobu modifikace záznamů v this.ChartElements nebude probíhat vizuální kalkulace návazných údajů:
                    if (this._ElementDict.ContainsKey(requestGId))
                    {
                        this._ElementDict.Remove(requestGId);
                        result = true;
                    }
                    if (result)
                        this._InvalidateAll();           // Invaliduji pracovní data. V případě potřeby se vytvoří nově.
                }
            }
			return result;
		}
		/// <summary>
		/// Zajistí vepsání chyby do temp elementu.
		/// Pokud v procesu zpracování požadavku (na donačtení elementů) dojde v datovém zdroji k chybě, 
		/// ponecháme temp element pro daný čas viditelný, ale upravíme jej (jiná barva), a doplníme do něj informace o chybě.
		/// </summary>
		/// <param name="elementGId"></param>
		/// <param name="exception"></param>
		internal void ChartElementsTempError(GID elementGId, Exception exception)
		{
			GridDataElementCls element = this.GetChartElement(elementGId, true);
			if (element != null)
			{
				// V elementu je několik property, které řídí stav Temp elementu:
				element.TempElementState = GraphTempElementState.TempError;
				element.ToolTipGraph = exception.Message;
				element.ToolTipGraphExtended = exception.Message + Environment.NewLine + exception.StackTrace;
			}
		}
		/// <summary>
		/// Smaže všechna data grafů z paměti, plus příslušné registry
		/// </summary>
		internal void ChartElementClear()
		{
			this._ChartElementClear(null);
		}
		/// <summary>
		/// Smaže všechna data grafů z paměti, plus příslušné registry.
		/// Ponechá v paměti ty elementy, které jsou uvedeny v daném seznamu rezerovovaných elementů.
		/// </summary>
		internal void ChartElementClear(IEnumerable<GID> reservedElements)
		{
			this._ChartElementClear(reservedElements);
		}
		/// <summary>
		/// Smaže všechna data grafů z paměti, plus příslušné registry.
		/// Ponechá v paměti ty elementy, které jsou uvedeny v daném seznamu rezerovovaných elementů.
		/// </summary>
		private void _ChartElementClear(IEnumerable<GID> reservedElements)
		{
			if (this._ElementDict == null)
				this._ElementDict = new Dictionary<GID, GridDataElementCls>();
			
			lock (this._ElementDict)
			{	// Zámek nám zajistí, že po dobu modifikace záznamů v this.ChartElements nebude probíhat vizuální kalkulace návazných údajů:
				Dictionary<GID, GridDataElementCls> reserved = new Dictionary<GID, GridDataElementCls>();
				if (reservedElements != null)
				{	// Sestavím index elementů, které chci zachránit:
					foreach (GID gId in reservedElements)
					{
						GridDataElementCls element;
						if (!reserved.ContainsKey(gId) && this._ElementDict.TryGetValue(gId, out element))
							reserved.Add(gId, element);
					}
				}
				// Clear(), nikoli   new Dictionary<GID, GridDataElementCls>();
				//  protože na objektu je lock !
				this._ElementDict.Clear();
				if (reserved.Count > 0)
				{	// Vrátím tam elementy, které se nemají ztratit:
					foreach (KeyValuePair<GID, GridDataElementCls> kvp in reserved)
						this._ElementDict.Add(kvp.Key, kvp.Value);
				}

				this.ChartElementLoadedTimeRange = TimeRange.Empty;
				this._AllElementsLoaded = false;
				this._InvalidateAll();
			}
		}
		/// <summary>
		/// Příznak, že elementy načtené do pole this.ElementList představují úplnou sadu elementů, 
		/// a pro tento řádek (this) nebude nikdy zapotřebí donačítat elementy pro širší časové pole.
		/// </summary>
		internal bool AllElementsLoaded
		{
			get { return _AllElementsLoaded; }
			set
			{
				if (_AllElementsLoaded && !value)
					Msg.Error("Není možno zrušit příznak «AllElementsLoaded» z hodnoty true na false.");
				_AllElementsLoaded = value;
			}
		}
		private bool _AllElementsLoaded;
		/// <summary>
		/// Zajistí, že budou připravena data všech elementů, a přepočtena výška řádku a všech linek.
		/// Nezajistí přípravu viditelných elementů.
		/// </summary>
		internal void AllListCheck()
		{
			this._ElementAllListCheck();
		}
		/// <summary>
		/// Vypočte a vrátí souhrnný časový interval všech (dosud načtených) elementů.
		/// </summary>
		internal TimeRange AllElementsTimeRange
		{
			get
			{
				TimeRange timeRange = TimeRange.Empty;
				foreach (GridDataElementCls element in this._ElementDict.Values)
					timeRange = timeRange + element.TimeRange;
				return timeRange;
			}
		}
		/// <summary>
		/// Obsahuje true, pokud v tomto řádku existuje nějaký element.
		/// </summary>
		internal bool ContainAnyElement { get { return (this._ElementDict.Count > 0); } }
		/// <summary>
		/// Vrátí true, pokud v tomto řádku existuje konkrétní element.
		/// Velice rychlá metoda. Neprovádí žádné donačítání a ani dokalkulace souřadnic elementu.
		/// Jen jednoduchý dotaz do interní dictionary.
		/// </summary>
		/// <param name="gID">GID elementu, na který se ptáme</param>
		/// <returns></returns>
		internal bool ContainElement(GID gID)
		{
			return this._ElementDict.ContainsKey(gID);
		}
        /// <summary>
        /// Zajistí invalidaci všech svých elementů = smazání elementů, plus zrušení času, za který jsou data načtena.
        /// </summary>
        /// <returns></returns>
        internal bool InvalidateAllElements()
        {
            bool hasData = (this._ElementDict.Count > 0);
            this.ChartElementClear();
            return hasData;
        }
        #endregion
		#region PRIVÁTNÍ SPRÁVA DAT : zpracování vstupních položek do interních seznamů, přepočty, rozmístění, výpočty Rectangle
		#region Příprava ElementAllList = seznam všech elementů, jejich rozložení v ose Y = skládání na sebe, výpočet výšky řádku.
		/// <summary>
		/// Metoda zajistí, že bude připraven seznam všech elementů, s přepočítanými souřadnicemi Top, Bottom (relativní).
		/// Pokud již nyní existuje, a není nutno jej připravit znovu, přípravu nedělá a okamžitě skončí.
		/// </summary>
		private void _ElementAllListCheck()
		{
			if (this._ElementAllListNeedPrepare)
				this._ElementAllListPrepare();
		}
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// Zamkne data (this.DataItemDict) a volá výkonnou metodu this._VisualItemListPrepareLock()
		/// </summary>
		private void _ElementAllListPrepare()
		{
			lock (this._ElementDict)
			{
				this._ElementAllListPrepareLock();
			}
		}
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// Očekává se, že data jsou po tuto dobu zamčená.
		/// </summary>
		private void _ElementAllListPrepareLock()
		{
			this._ElementAllList = new List<GridDataElementCls>();
			switch (this.RowGraph)
			{
				case RowGraphMode.Percentage:
				case RowGraphMode.SingleTask:
				case RowGraphMode.UserDraw:
					this._ElementAllListPrepareStandard();
					break;
				case RowGraphMode.MultiTask:
					this._ElementAllListPrepareMultiTask();
					break;
				case RowGraphMode.BarGraph:
					this._ElementAllListPrepareBarGraph();
					break;
				case RowGraphMode.NoDrawingElements:
				default:
					this._ElementAllListPrepareNoDrawing();
					break;
			}
		}
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// je určeno pro obecné grafy, ne pro MultiTask.
		/// </summary>
        private void _ElementAllListPrepareNoDrawing()
        {
            this._ElementAllList = new List<GridDataElementCls>();
            this.UseRatioSumMax = 1M;
            this.UseRatioOneMin = 1M;
            this.ContainFragments = false;

            this._RowHeightRecalc();

            // Shodím explicitní příznak, který vede k mému spouštění:
            this.RequestCalcVertical = false;

            // Zajistím invalidaci current vizuálního seznamu (tím zajistím nutnost jeho znovuvytvoření):
            this._ElementVisibleLayeredDict = null;
        }
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// je určeno pro obecné grafy, ne pro MultiTask.
		/// </summary>
		private void _ElementAllListPrepareStandard()
		{
			List<GridDataElementCls> dataList = new List<GridDataElementCls>(this._ElementDict.Values);
			// Setřídit všechny elementy podle času počátku
			dataList.Sort(GridDataElementCls.CompareByBegin);

			decimal sumMax = 0M;
			decimal oneMin = 0M;
			bool containFragments = false;

			// Pracovní seznam s informacemi o nyní přítomných položkách ve vizuálním seznamu, v okolí aktuálního času (tj. data.Begin):
			// Obsahuje položky = předchozí elementy (předchozí dle času jejich Begin), a které trvají přinejmenším v čase Begin tohoto elementu.
			// Popisuje jejich vertikální pozici v grafu, v relativních hodnotách (kde 1 = jedna výrobní linka = stroj),
			// a pomáhá tak určovat relativní pozici pro nově umisťovaný element = tak, aby se vešel mezi stávající elementy, anebo se umístil nad ně.
			// Skládá tak elementy na sebe jako v Tetrisu, s tím že element může propadnout i do nižšího "řádku", pokud se tam celý vejde
			//  (což je prioritní postup).
			List<_UseItem> useList = new List<_UseItem>();

			// Pro každý element (bez ohledu na jejich viditelnost v aktuální časové ose):
			foreach (GridDataElementCls data in dataList)
			{
				// V tuto chvíli není žádný element viditelný (na konci této metody se do this._CurrentVisualItemList zapisuje null):
				// Viditelnost elementů se řeší v metodě this._CurrentVisualItemListPrepareLock(), tam se budou setovat viditelné elementy.
				data.SetVisibleAxis(false);

				// Prvek se má vložit do soupisu _VisualItemList:
				data.StoreRelativeY(0M, 1M);
				this._ElementAllList.Add(data);
			}

			// Zapamatuji si mezní hodnoty a zajistím přepočet výšky řádku:
			this.UseRatioSumMax = sumMax;
			this.UseRatioOneMin = oneMin;
			this.ContainFragments = containFragments;

			this._RowHeightRecalc();

			// Setřídím výsledné položky podle vrstvy a času (SubLayer a TempLayer půjdou dospodu), 
            // v jedné vrstvě pak vše podle času Begin, a při shodném času Begin pak nejdřív ty, které mají nejvzdálenější End:
			// this._ElementAllList.Sort(GridDataElementCls.CompareByLayerBegin);

			// Shodím explicitní příznak, který vede k mému spouštění:
			this.RequestCalcVertical = false;

			// Zajistím invalidaci current vizuálního seznamu (tím zajistím nutnost jeho znovuvytvoření):
            this._ElementVisibleLayeredDict = null;
		}
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// je určeno pro graf typu MultiTask.
		/// </summary>
		private void _ElementAllListPrepareMultiTask()
		{
			// Graf typu MultiTask podporuje grupování elementů.
			// Jde o to, že elementy jedné grupy (IDataElement.ElementGroup) jsou rovnány do jedné vodorovné řady,
			//  do které se nemíchají elementy z jiné řady.
			// Vizuální uspořádání skupin (Group) je tedy odděleně nad sebou, s proměnnou výškou grupy podle potřeby
			//  (anebo se grupa vůbec nemusí v řádku vyskytovat).

			// Pracovní balíček s průběžnými daty:
			_MultiThreadPrepareArgs args = new _MultiThreadPrepareArgs();
		
			// Zpracování: 
			// Z kompletního balíku elementů se vytvoří seznam grup, a elementy se zpracovávají vždy pouze v rámci grupy:
			List<_MultiThreadGroup> groups = _MultiThreadGroup.CreateGroups(this._ElementDict.Values);
			foreach (_MultiThreadGroup group in groups)
				this._ElementAllList.AddRange(group.PrepareElements(args));

			// Zapamatuji si mezní hodnoty a zajistím přepočet výšky řádku:
			this.UseRatioSumMax = (args.UseRatioSumMax < 1M ? 1M : args.UseRatioSumMax);
			this.UseRatioOneMin = args.UseRatioOneMin;
			this.ContainFragments = args.ContainFragments;

			this._RowHeightRecalc();

			// Setřídím výsledné položky podle vrstvy a času (SubLayer a TempLayer půjdou dospodu), 
			// v jedné vrstvě pak vše podle času Begin, a při shodném času Begin pak nejdřív ty, které mají nejvzdálenější End:
			// this._ElementAllList.Sort(GridDataElementCls.CompareByLayerBegin);

			// Shodím explicitní příznak, který vede k mému spouštění:
			this.RequestCalcVertical = false;

			// Zajistím invalidaci current vizuálního seznamu (tím zajistím nutnost jeho znovuvytvoření):
			this._ElementVisibleLayeredDict = null;
		}
		/// <summary>
		/// Z dat ve slovníku s datovými prvky připraví vizuální seznam.
		/// je určeno pro graf typu BarGraph.
		/// </summary>
		private void _ElementAllListPrepareBarGraph()
		{
			List<GridDataElementCls> dataList = new List<GridDataElementCls>(this._ElementDict.Values);
			// Setřídit všechny elementy podle času počátku
			dataList.Sort(GridDataElementCls.CompareByBegin);

			decimal sumMax = 0M;

			// Pro každý element (bez ohledu na jejich viditelnost v aktuální časové ose):
			foreach (GridDataElementCls data in dataList)
			{
				// V tuto chvíli není žádný element viditelný (na konci této metody se do this._CurrentVisualItemList zapisuje null):
				// Viditelnost elementů se řeší v metodě this._CurrentVisualItemListPrepareLock(), tam se budou setovat viditelné elementy.
				data.SetVisibleAxis(false);

                // max(ratio)
                decimal ratio = ((decimal)data.DataElement.ElementHeightRatio);
                if (ratio > sumMax)
                    sumMax = ratio;

				// Prvek se má vložit do soupisu _VisualItemList:
				data.StoreRelativeY(0M, ratio);
				this._ElementAllList.Add(data);
			}

			// Zapamatuji si mezní hodnoty a zajistím přepočet výšky řádku:
			this.UseRatioSumMax = sumMax;
			this.UseRatioOneMin = 0.5m;
			this.ContainFragments = true;

			this._RowHeightRecalc();

			// Setřídím výsledné položky podle vrstvy a času (SubLayer a TempLayer půjdou dospodu), 
			// v jedné vrstvě pak vše podle času Begin, a při shodném času Begin pak nejdřív ty, které mají nejvzdálenější End:
			// this._ElementAllList.Sort(GridDataElementCls.CompareByLayerBegin);

			// Shodím explicitní příznak, který vede k mému spouštění:
			this.RequestCalcVertical = false;

			// Zajistím invalidaci current vizuálního seznamu (tím zajistím nutnost jeho znovuvytvoření):
			this._ElementVisibleLayeredDict = null;
		}
		#endregion
		#region Příprava UnitLine = seznam linek v rámci řádku, které reprezentují jednotku výšky, používá se při fragmentovaných elementech (které používají výšku != 1)
		/// <summary>
		/// Metoda zajistí, že bude připraven seznam souřadnic linek.
		/// Pokud již nyní existuje, a není nutno jej připravit znovu, přípravu nedělá a okamžitě skončí.
		/// </summary>
		private void _UnitLineListCheck()
		{
			if (this._UnitLineListNeedPrepare)
				this._UnitLineListPrepare();
		}
		/// <summary>
		/// Připraví data do seznamu this._CurrentLineList.
		/// Zamkne data (this.DataItemDict) a volá výkonnou metodu this._CurrentLineListPrepareLock()
		/// </summary>
		private void _UnitLineListPrepare()
		{
			lock (this._ElementDict)
			{
				this._UnitLineListPrepareLock();
			}
		}
		/// <summary>
		/// Připraví data do seznamu this._CurrentLineList.
		/// Očekává se, že data jsou po tuto dobu zamčená.
		/// </summary>
		private void _UnitLineListPrepareLock()
		{
			// Pokud někdo chce získat LineList, ale dosud neexistuje VisualList a tím pádem ani korektní výpočet výšek 
			//   (neproběhla tedy metoda _RowHeightRecalc()), je třeba vygenerovat this._VisualItemList a přepočítat výšky:
			if (this._ElementAllListNeedPrepare)
				this._ElementAllListPrepareLock();

			this._UnitLineList = new List<int>();

			int line = 0;
			decimal max = this.LineHeightCurrRatio;
			while (((decimal)line) <= max)
				this._UnitLineList.Add(this._DimYRecalc(line++) - 1);

			// Uložíme si vizuální dimenze, pro které jsme seznam this._CurrentVisualItemList přepočítali (tím zabráním opakovanému spouštění:
			this._CalcLineListRowHeight = this.RowHeightPx;
			this._CalcLineListLineHeight = this.LineHeightCurrPx;
			this._CalcLineHeightCurrRatio = this.LineHeightCurrRatio;
		}
		#endregion
		#region Příprava VisualItemList = seznam elementů, které v tomto řádku spadají do aktuálně viditelné oblasti z hlediska času, Výpočet souřadnic X.
		/// <summary>
		/// Metoda zajistí, že bude připraven seznam viditelných elementů.
		/// Pokud již nyní existuje, a není nutno jej připravit znovu, přípravu nedělá a okamžitě skončí.
		/// </summary>
		private void _CurrentVisualItemListCheck()
		{
			if (this._ElementVisibleListNeedPrepare)
				this._CurrentVisualItemListPrepare();
		}
		/// <summary>
		/// Připraví data do seznamu this._CurrentVisualItemList.
		/// Seznam bude obsahovat ty prvky seznamu this.VisualItemList, které spadají do oblasti viditelného časového intervalu this.VisibleTimeRange.
		/// Zamkne data (this.DataItemDict) a volá výkonnou metodu this._CurrentVisualItemListPrepareLock()
		/// </summary>
		private void _CurrentVisualItemListPrepare()
		{
			lock (this._ElementDict)
			{
				this._CurrentVisualItemListPrepareLock();
			}
		}
		/// <summary>
		/// Metoda vrací seznam elementů dané vrstvy.
		/// Z důvodů threadového zabezpečení zamyká klíčový objekt po celou dobu od detekce přes přírpavu do získání výsledku.
		/// </summary>
		/// <param name="layer"></param>
		/// <returns></returns>
		private List<GridDataElementCls> _GetElementVisibleListForLayerLocked(GraphLayerType layer)
		{
			lock (this._ElementDict)
			{
				if (this._ElementVisibleListNeedPrepare)          // zde se detekují podmínky pro nutnost nové přípravy
					this._CurrentVisualItemListPrepareLock();     // příprava dat, bez řešení zámku

				// Vyhledání ve výsledné tabulce:
				List<GridDataElementCls> layerList;
				if (this._ElementVisibleLayeredDict != null && this._ElementVisibleLayeredDict.TryGetValue(layer, out layerList))
					return layerList;
			}
			return null;
		}
		/// <summary>
		/// Připraví data do seznamu this._CurrentVisualItemList.
		/// Seznam bude obsahovat ty prvky seznamu this.VisualItemList, které spadají do oblasti viditelného časového intervalu this.VisibleTimeRange.
		/// Očekává se, že data jsou po tuto dobu zamčená.
		/// </summary>
		private void _CurrentVisualItemListPrepareLock()
		{
			if (this.TimeAxisCalc != null)
			{
				// Pořadí nelze měnit: při získávání seznamu this.VisualItemList může dojít k tomu, že se do this._CurrentVisualItemList vloží null.
				List<GridDataElementCls> visualItemList = this.ElementAllList;                                 // Zde může proběhnout příprava dat do seznamu this.VisualItemList (viz get; )
                this._ElementVisibleLayeredDict = new Dictionary<GraphLayerType, List<GridDataElementCls>>();  // V rámci přípravy seznamu VisualItemList mohl výt vynulován objekt this._CurrentVisualItemList.

				int rowHeight = this.RowHeightPx;
				int lineHeight = this.LineHeightCurrPx;
				TimeRange visibleTimeRange = this.TimeAxisCalc.TimeRange;
				foreach (GridDataElementCls element in visualItemList)
				{
					// Dimenze se vkládají i do neviditelných elementů:
					OneDimensionI dimX = this._DimXRecalcLeftRight(element);    // Souřadnice X jsou odvozeny od času elementu a od časové osy (anebo FullRow)
					OneDimensionI dimY = this._DimYRecRow(element);             // Souřadnice Y přes celý řádek
					element.StoreElementRowArea(dimX, dimY);                    // Do ElementRowArea se uloží pozice přes celý řádek
					dimY = this._DimYRecRel(element);                           // Souřadnice Y podle relativních souřadnic
					element.StoreElementRelArea(dimX, dimY);                    // Do ElementRelArea se uloží relativní pozice

					// Které elementy budeme vkládat do viditelného soupisu:
					//  a) elementy FullRow, 
					//  b) anebo elementy, jejichž čas spadá do viditelné oblasti grafu, a jejich šířka je viditelná: 
					if (element.FullRow || (visibleTimeRange.IsConjunct(element.TimeRange) && element.AddToVisibleByWidth()))
					{
                        // Do indexovaného seznamu podle vrstvy:
                        List<GridDataElementCls> layerList;
                        if (!this._ElementVisibleLayeredDict.TryGetValue(element.ElementLayer, out layerList))
                        {
                            layerList = new List<GridDataElementCls>();
                            this._ElementVisibleLayeredDict.Add(element.ElementLayer, layerList);
                        }
                        layerList.Add(element);
					}
				}

				// Uložíme si vizuální dimenze, pro které jsme seznam this._CurrentVisualItemList přepočítali (tím zabráním opakovanému spouštění:
				this._CalculatedTimeAxisState = this.TimeAxisCalc.CurrentState;  // Jde o struct, uloží se kopie údaje vráceného z časové osy
				this._CalculatedLineHeight = this.LineHeightCurrPx;
				this._CalculatedRowHeight = this.RowHeightPx;

                // Elementy ve vizuálních seznamech setřídím dle času a umožním provést úpravy jejich dimenzí, pokud jsou elementy příliš úzké a mají okolo sebe nějaký prostor:
                foreach (List<GridDataElementCls> elementList in this._ElementVisibleLayeredDict.Values)
                    this._CurrentVisualItemListPrepareFinal(elementList);

				// Shodím explicitní příznak, který vede k mému spouštění:
				this.RequestCalcHorizontal = false;
			}
			else
			{
                this._ElementVisibleLayeredDict = new Dictionary<GraphLayerType, List<GridDataElementCls>>();
			}
		}
        /// <summary>
        /// V daném seznamu vizuálních elementů provede finální přípravu.
        /// Elementy jsou vybrány jako vhodné k zobrazení. Mají korektně určeny souřadnice podle časové osy.
        /// V průběhu této metody se seznam setřídí dle souřadnice Top a pak dle času počátku (souřadnice Left).
        /// Poté pro elementy se šířkou 3 pixely a menší zjistí, zda by je bylo možno rozšířit (podle místa, které okolo sebe mají).
        /// Následně určí jednak rozšíření, a jednak možnost modifikace vnitřní kresby pro elementy, jejichž finální šířka je 3 px a menší.
        /// Elementy se šířkou 0 px nastaví jako neviditelné.
        /// </summary>
        /// <param name="elementList"></param>
        private void _CurrentVisualItemListPrepareFinal(List<GridDataElementCls> elementList)
        {
            // Setřídit podle Y zdola nahoru a pak podle X zleva doprava:
			switch (this.RowGraph)
			{
				case RowGraphMode.NoDrawingElements:
					break;
				case RowGraphMode.UserDraw:
					elementList.Sort(GridDataElementCls.CompareByBegin);
					break;
				case RowGraphMode.SingleTask:
					elementList.Sort(GridDataElementCls.CompareByBegin);
					break;
				case RowGraphMode.Percentage:
					elementList.Sort(GridDataElementCls.CompareByBegin);
					break;
				case RowGraphMode.Line:
					elementList.Sort(GridDataElementCls.CompareByBegin);
					break;
				case RowGraphMode.MultiTask:
					elementList.Sort(GridDataElementCls.CompareByPosition);
					break;
				case RowGraphMode.BarGraph:
					elementList.Sort(GridDataElementCls.CompareByBegin);
					break;
			}

            // Nyní projdu elementy, a pokud některý má šířku menší než 3 pixely, zkusím nad ním pouvažovat:
            int c = elementList.Count;
            int l = c - 1;
            for (int e = 0; e < c; e++)
            {
                GridDataElementCls ec = elementList[e];
                int w = ec.ElementRelArea.Width;
                if (ec.HasOnlyImage)
                {   // Pouze obrázek: Nastavím viditelnost elementu na true, zobrazíme jen Image:
                    ec.SetVisibleAxis(true);
                }
                else if (w >= 3)
                {   // Dostatečně široký element: bude se zobrazovat, a jeho šířka se nemusí upravovat. Nastavím viditelnost elementu na true:
                    ec.SetVisibleAxis(true);
                }
                else if (!ec.TimeInvisible)
                {   // Máme element, který je úzký (0,1,2 pixely), ale jeho čas je nula nebo kladný => měl by být vidět:
                    // Najdu element vlevo a vpravo, který je v naší vizuální vodorovné lince (ignoruji elementy, s nimiž se nepotkávám na souřadnicích Top, Bottom):
                    int ect = ec.ElementRelArea.Top;
                    int ecb = ec.ElementRelArea.Bottom;
                    GridDataElementCls el = (e == 0 ? null : elementList[e - 1]);
                    if (el != null && (el.ElementRelArea.Top >= ecb || el.ElementRelArea.Bottom <= ect))
                        el = null;
                    GridDataElementCls er = (e == l ? null : elementList[e + 1]);
                    if (er != null && (er.ElementRelArea.Top >= ecb || er.ElementRelArea.Bottom <= ect))
                        er = null;

                    // Funkce pro "široké lokty" u malého elementu je zde:
                    // Zajistí to, že pokud element vlevo a/nebo vpravo je buď dostatečně daleko, anebo může ustoupit, udělá to a uvolní kousek místa pro náš element.
                    // Pokud se to podaří, bude náš element o kousek širší a bude viditelný. Pokud mu zůstane šířka 0px, pak se nebude zobrazovat (a nebude zatěžovat vizuální vrstvu).
                    _CurrentVisualItemWideElement(ec, el, er);
                }
                if (ec.IsInVisibleAxis)
                    ec.InternalDimensionCalculate();         // Automaticky doplním interní dimenze  
            }

            // Odeberu elementy, které nejsou viditelné:
            elementList.RemoveAll(el => !el.IsInVisibleAxis);
        }
        /// <summary>
        /// Pokusí se rozšířit aktuální element tak, aby byl vidět.
        /// Na vstupu je element se šířkou 0,1,2 pixely.
        /// Element vlevo a/nebo vpravo může být null (pak v daném směru nic nebrání rozšíření).
        /// Sousední elementy mohou být těsně vedle, nebo může být mezi nimi mezera.
        /// Aktuální element rozšiřuje nejvíce o 1 pixel na každou stranu, přičemž element s výchozí šířkou 2 pixely rozšiřuje jen na jednu stranu = 3 pixely stačí.
        /// Pokud je sousední element na těsno vedle, pak se může uhnout jen tehdy, pokud jeho šířka po zmenšení bude 3 pixely a více.
        /// Přičemž platí, že sousední element vpravo může uhnout pouze do šířky 4 pixely, protože může být požádán o zmenšení i z druhé strany 
        /// (tj. když se ocitne vlevo), a pak uhne o další pixel na 3 pixely.
        /// Pokud aktuální element bude mít po rozšíření stále šířku 0, nebude nastaven jako viditelný.
        /// </summary>
        /// <param name="elCurr">Element, který by se měl rozšířit</param>
        /// <param name="elPrev">Element, který stojí vlevo vedle něj</param>
        /// <param name="elNext">Element, který stojí vpravo vedle něj</param>
        private void _CurrentVisualItemWideElement(GridDataElementCls elCurr, GridDataElementCls elPrev, GridDataElementCls elNext)
        {
            // Poznámka k souřadnicím (pro úplné grafické analfabety):
            // Left je pixel, kde začíná kresba
            // Width je počet obarvených pixelů (čtverečky na papíře)
            // Right je pixel, kde už kresba není !!!   To je souřadnice prvního pixelu vpravo od posledního obarveného pixelu.
            //     Pokud další element začíná (má Left) na souřadnici Right předešlého elementu, pak na sebe těsně navazují, ale nepřekážejí si.
            // Má-li element vlevo uhnout a udělat nám místo, pak jde o následující příklad:
            //  Prev.Left  = 20
            //  Prev.Width =  5
            //  Prev.Right = 25
            //  Curr.Left  = 25
            //  Curr.Width =  0
            // Element vlevo má svůj pravý okraj (poslední obarvený pixel) na pixelu 24.
            // Náš element by rád začínal na pixelu 25, má šířku 0. (Zatím neřeším, co je vpravo vedle něj.)
            // Element vlevo je dost široký, může ustoupit o 1 pixel, pak budou souřadnice:
            //  Prev : Left=20, Width=4, Right=24, poslední obarvený pixel = (Right-1)=23
            //  Curr : Left=24, Width=1, Right=25, jediný obarvený pixel = 24

            // Souřadnice obarveného pixelu vlevo a vpravo:
            int prevLast = (elPrev == null ? elCurr.ElementRelArea.Left - 10 : elPrev.ElementRelArea.Right - 1);   // Poslední pixel elementu Prev (anebo null => dostatečný prostor 10 px)
            int nextFirst = (elNext == null ? elCurr.ElementRelArea.Right + 10 : elNext.ElementRelArea.Left);      // První pixel elementu Next (anebo null => dostatečný prostor 10 px)

            // Doleva:
            if (elCurr.ElementRelArea.Width <= 2)
            {
                bool wl = false;
                if (prevLast < elCurr.ElementRelArea.Left - 1)
                {   // Vlevo nic není, anebo je a poslední obarvený pixel (prevLast) je dostatečně daleko vlevo ( -1 ), abych mohl Current rozšířit doleva (o ten 1 pixel):
                    wl = true;
                }
                else if (prevLast == elCurr.ElementRelArea.Left && elPrev.ElementRelArea.Width >= 4)
                {   // Vlevo je element, který má Right == naše Left (náš by chtěl začínat na jeho posledním pixelu), a ten vlevo je dostatečně široký:
                    elPrev.ElementRelArea.Width--;
                    prevLast--;
                    wl = true;
                }
                if (wl)
                {
                    elCurr.ElementRelArea.X--;
                    elCurr.ElementRelArea.Width++;
                }
            }

            // Doprava:
            if (elCurr.ElementRelArea.Width <= 2)
            {
                bool wr = false;
                if (nextFirst > elCurr.ElementRelArea.Right)
                {   // Vpravo nic není, anebo je a první obarvený pixel (nextFirst) je dostatečně daleko vpravo za naší souřadnicí Right (to je pozice prvního pixelu, který element curr neobarvuje):
                    wr = true;
                }
                else if (nextFirst == elCurr.ElementRelArea.Right && elNext.ElementRelArea.Width >= 5)
                {   // Vpravo je element, který má Left == naše Right (náš by se rád posunul doprava na jeho pixel Left), a ten vpravo je dostatečně široký:
                    elNext.ElementRelArea.X++;
                    elNext.ElementRelArea.Width--;
                    nextFirst++;
                    wr = true;
                }
                if (wr)
                {
                    elCurr.ElementRelArea.Width++;
                }
            }

            // Viditelný element je jen takový, který má kladnou šířku (tady neřeším nulové viditelné elementy, ale jen elementy, které jsou vidět díky svému tvaru)
            if (elCurr.ElementRelArea.Width > 0)
                elCurr.SetVisibleAxis(true);
        }
		#endregion
		#region Dopočty výšky linek a výšky řádku, kalkulace na ose X (časové) a Y (relativní pozice elementu na pozici v pixelech v rámci řádku).
		/// <summary>
        /// Metoda přepočte podle aktuálních dat výšku řádku a uloží ji do proměnné this.RowHeightPx
        /// </summary>
        private void _RowHeightRecalc()
		{
			// Výška řádku v ratio čísle (0-1) = this.UseRatioSumMax zaokrouhlené nahoru na celé číslo, nejméně 1, s přičtením AddLinesRatio:
			bool containUseRatio = this.ContainUseRatio;             // true = tento řádek obsahuje fragmenty s UseRatio < 1
			decimal useRatioSumMax = (this.UseRatioSumMax < 1M ? 1M : this.UseRatioSumMax);
			decimal addLinesRatio = (!containUseRatio ? this.RowHeightAddLineSolidRatio : this.RowHeightAddLineFragmentRatio); // Přídavek k řádku v poměru k výšce linky (0-1)
			decimal lineHeightPx = (!containUseRatio ? this.RowHeightLineSolidPx : this.RowHeightLineFragmentPx);              // Výška linky v pixelech (nedělená = Solid / dělená = Fragment)
			decimal relativeRowHeight = Math.Ceiling(useRatioSumMax) + addLinesRatio;                                          // Výška řádku relativní

			// Kontrola nejmenšího fragmentu, určení výšky linky:
			decimal minRatio = this.UseRatioOneMin;
			if (minRatio > 0M && minRatio < 1M)
			{
				decimal lineHeightDc = (decimal)lineHeightPx;
				decimal minFragmentDc = (decimal)this.RowHeightMinFragmentPx;
				decimal minFragmentHeight = minRatio * lineHeightDc; // Jak by byl vysoký nejmenší fragment
				if (minFragmentHeight < minFragmentDc)
				{	// Zvětším výšku linky tak, aby nejmenší fragment byl vysoký this.RowHeightMinFragmentPx:
					lineHeightPx = (int)(Math.Ceiling(minFragmentDc / minRatio));
				}
			}
			lineHeightPx = (lineHeightPx < 7M ? 7M : (lineHeightPx > 100M ? 100M : lineHeightPx));   // Do rozmezí 7 - 100, včetně

			// Výška řádku v pixelech, vypočtená bez korekce:
			decimal rowHeightPx = relativeRowHeight * lineHeightPx;

			// Korekce na minimální a maximální výšku řádku v pixelech:
			if (this.RowHeightTotalMaxPx > 10 && rowHeightPx > this.RowHeightTotalMaxPx) // Máme horní limit pro výšku, a překročili jsme ho?
			{	// Výška řádku bude == maximum, a přepočteme koeficient výšky jedné linky:
				rowHeightPx = this.RowHeightTotalMaxPx;
				lineHeightPx = rowHeightPx / relativeRowHeight;
			}
            else if (this.RowHeightTotalMinPx > 10 && rowHeightPx < this.RowHeightTotalMinPx)
            {   // Výška řádku je menší než je nastavené minimum: zvýšíme řádek, ale linky nepřepočítáváme:
                rowHeightPx = this.RowHeightTotalMinPx;
            }

			// Uložíme výsledky:
			this._RowHeightPx = ((int)(Math.Round(rowHeightPx, 0))) + 2;
			this.LineHeightCurrPx = (int)(Math.Round(lineHeightPx, 0));
			this.LineHeightCurrRatio = relativeRowHeight;

			// Zajistíme vyvolání přepočtu _CurrentLineList:
			this._UnitLineList = null;
		}
		/// <summary>
		/// Provede přepočet souřadnic ve směru X pro danou položku.
		/// </summary>
		private OneDimensionI _DimXRecalcLeftWidth(GridDataElementCls item)
		{
			if (this.TimeAxisCalc == null) return OneDimensionI.Empty;
			
			int left = 0;
			int width = 0;

			if (!item.FullRow)
			{	// Pokud daný element není FullRow, pak se souřadnice na ose X počítají podle data TimeRange:
				left = this.TimeAxisCalc.GetPixelForTime(item.TimeRange.Begin);
				width = this.TimeAxisCalc.GetPixelForSeconds(item.TimeRange.Seconds);
			}
			else
			{	// Element je FullRow = bez ohledu na jeho čas je tento element vždy vidět přes celý grafický sloupec v řádku:
				left = 0;
				width = this.TimeAxisCalc.VisualWidth;
			}
			return new OneDimensionI(left, width);
		}
		/// <summary>
		/// Provede přepočet souřadnic ve směru X pro danou položku.
		/// </summary>
		private OneDimensionI _DimXRecalcLeftRight(GridDataElementCls item)
		{
			if (this.TimeAxisCalc == null) return OneDimensionI.Empty;

			int left = 0;
			int right = 0;

			if (!item.FullRow)
			{	// Pokud daný element není FullRow, pak se souřadnice na ose X počítají podle data TimeRange:
				left = this.TimeAxisCalc.GetPixelForTime(item.TimeRange.Begin, item.BeginOffsetMilisec);
				right = this.TimeAxisCalc.GetPixelForTime(item.TimeRange.End, item.EndOffsetMilisec);
			}
			else
			{	// Element je FullRow = bez ohledu na jeho čas je tento element vždy vidět přes celý grafický sloupec v řádku:
				left = 0;
				right = this.TimeAxisCalc.VisualWidth;
			}
			return new OneDimensionI(left, right - left);
		}
		/// <summary>
		/// Provede přepočet souřadnic ve směru Y pro danou položku.
		/// Vypočte souřadnice Y pro výšku elementu přes celý řádek: počínaje Top = 1, Height = RowHeightPx - 2.
		/// Naproti tomu metoda this._DimYRecRel() určí pozici Y podle relativních souřadnic RelativeTop, RelativeBottom.
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		private OneDimensionI _DimYRecRow(GridDataElementCls item)
		{
			int rowHeight = this._RowHeightPx - 2;
			int lineHeight = rowHeight;

			int top = (int)Math.Round((decimal)lineHeight * item.RelativeTop, 0);          // Top měřeno odspodu nahoru (opačně než WinForm)
			int bottom = (int)Math.Round((decimal)lineHeight * item.RelativeBottom, 0);    // Bottom, stejně tak
			OneDimensionI dimY = new OneDimensionI(rowHeight - top + 1, top - bottom);     // Převést na souřadnice WinForm, se základnou rowHeight
			return dimY;
		}
		/// <summary>
		/// Provede přepočet souřadnic ve směru Y pro danou položku.
		/// Vypočte souřadnice Y pro výšku elementu podle relativních souřadnic RelativeTop, RelativeBottom.
		/// Naproti tomu metoda this._DimYRecRow() určí pozici Y přes celý řádek: počínaje Top = 1, Height = RowHeightPx - 2.
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		private OneDimensionI _DimYRecRel(GridDataElementCls item)
		{
			int rowHeight = this.RowHeightPx - 2;
			int lineHeight = this.LineHeightCurrPx;

			int top = (int)Math.Round((decimal)lineHeight * item.RelativeTop, 0);          // Top měřeno odspodu nahoru (opačně než WinForm)
			int bottom = (int)Math.Round((decimal)lineHeight * item.RelativeBottom, 0);    // Bottom, stejně tak
			OneDimensionI dimY = new OneDimensionI(rowHeight - top + 1, top - bottom);     // Převést na souřadnice WinForm, se základnou rowHeight
			return dimY;
		}
		/// <summary>
		/// Provede přepočet souřadnic ve směru Y pro danou položku.
		/// Na vstupu je relativní pozice, počínaje 0 = dole, 1 = jeden logický řádek.
		/// Na výstupu je absolutní pozice v pixelech, 0 = nahoře.
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		private int _DimYRecalc(decimal ratio)
		{
			int rowHeight = this.RowHeightPx;
			int lineHeight = this.LineHeightCurrPx;

			int y = (int)Math.Round((decimal)lineHeight * ratio, 0);
			return rowHeight - y;
		}
		#endregion
		#region HLÍDÁNÍ ZMĚNY VIZUÁLNÍHO PROSTORU PRO DATA (TimeRange, Width, RowHeight, LineHeight) - paměť hodnot, pro které jsou provedeny přepočty. Detekce změny.
		/// <summary>
		/// Aktuální stav časové osy (časový interval a jeho vizuální šířka).
		/// Stav lze porovnat (==, !=) a lze jej ukládat do privátní proměnné, jde o strukturu takže se uloží kopie.
		/// Pro tento čas je nyní přepočtem seznam ElementVisibleList.
		/// </summary>
		private TimeRangeWidth _CalculatedTimeAxisState;
		/// <summary> { get; }
		/// Aktuální hodnota výšky viditelného řádku, celý řádek, v pixelech.
		/// Pro tuto výšku je nyní přepočten seznam ElementVisibleList.
		/// </summary>
		private int _CalculatedRowHeight { get; set; }
		/// <summary> { get; }
		/// Aktuální hodnota výšky jedné linky (ekvivalent hodnoty UseRatio = 1), v pixelech.
		/// Pro tuto výšku je nyní přepočten seznam ElementVisibleList.
		/// </summary>
		private int _CalculatedLineHeight { get; set; }
		/// <summary> { get; }
		/// Aktuální hodnota výšky viditelného řádku, celý řádek, v pixelech.
		/// Pro tuto výšku je nyní přepočten seznam UnitLineList.
		/// </summary>
		private int _CalcLineListRowHeight { get; set; }
		/// <summary> { get; }
		/// Aktuální hodnota výšky jedné linky (ekvivalent hodnoty UseRatio = 1), v pixelech.
		/// Pro tuto výšku je nyní přepočten seznam UnitLineList.
		/// </summary>
		private int _CalcLineListLineHeight { get; set; }
		/// <summary>
		/// Aktuální hodnota počtu linek (Ratio), ne pixely.
		/// Pro tento čas je nyní přepočten seznam UnitLineList.
		/// </summary>
		private decimal _CalcLineHeightCurrRatio { get; set; }
		/// <summary>
		/// Vrací příznak, zda je třeba zavolat metodu pro přípravu dat do this._VisualItemList (trvalý seznam dat).
		/// </summary>
		/// <returns></returns>
		private bool _ElementAllListNeedPrepare
		{
			get
			{
				if (this._ElementAllList == null) return true;
				if (this.RequestCalcVertical) return true;
				return false;
			}
		}
		/// <summary>
		/// Vrací příznak, zda je třeba zavolat metodu pro přípravu dat do this._CurrentLineList (seznam pozic jednotlivých Lines).
		/// </summary>
		/// <returns></returns>
		private bool _UnitLineListNeedPrepare
		{
			get
			{
				if (this._UnitLineList == null) return true;

				if (this._CalcLineListRowHeight != this.RowHeightPx) return true;            // Změna výšky řádku
				if (this._CalcLineListLineHeight != this.LineHeightCurrPx) return true;      // Změna výšky linky
				if (this._CalcLineHeightCurrRatio != this.LineHeightCurrRatio) return true;  // Změna počtu linek

				return false;
			}
		}
		/// <summary>
		/// Vrací příznak, zda je třeba zavolat metodu pro přepočet aktuálních dat do this._CurrentVisualItemList (nyní viditelný seznam dat).
		/// </summary>
		/// <returns></returns>
		private bool _ElementVisibleListNeedPrepare
		{
			get
			{
                if (this._ElementVisibleLayeredDict == null) return true;
				if (this.RequestCalcHorizontal) return true;

				// Pokud není připravena časová osa, vrátím false. 
				// Pokud je připravena, a má jiné rozměry než pro které jsme posledně kalkulovali naše data, vrátím true = příprava se provede.
				if (this.TimeAxisCalc == null) return false;

				// Změna kalkulovaných dat proti datům aktuální osy:
				if (this._CalculatedTimeAxisState != this.TimeAxisCalc.CurrentState) return true;  // Změna časové osy
				if (this._CalculatedLineHeight != this.LineHeightCurrPx) return true;      // Změna výšky linky
				if (this._CalculatedRowHeight != this.RowHeightPx) return true;            // Změna výšky řádku

				return false;
			}
		}
		#endregion
		#endregion
		#region FIND ELEMENT BY LOCATION, TIME
		/// <summary>
		/// Metoda najde a vrátí item z pole Current, které se nachází na dané vizuální lokalitě (dané konkrétním bodem).
		/// Souřadnice daného bodu musí odpovídat souřadnicím položek v poli this.ElementVisibleList.
		/// Pokud si nadřízená aplikace souřadnice prvků upravuje 
		/// (což pravděpodobně činí, protože zdejší souřadnice mají počátek = bod 0/0 v počátku řádku, 
		/// ale volající aplikace kreslí řádek do jiného počátku = řádek putuje po parent controlu),
		/// pak musí volající aplikace reverzním způsobem převést souřadnici bodu tak, aby byla vztažena k počátku 0/0 tohoto řádku.
		/// Pokud je na daném bodě více prvků tohoto řádku, pak tato funkce vrací první z nich.
		/// Pokud na daném bodě není žádný prvek, vrací null.
		/// Pokud je na daném bodě více prvků, a chceme získat jejich seznam, je třeba volat metodu FindCurrentItemListByLocation().
		/// </summary>
		/// <param name="point"></param>
		/// <returns></returns>
		internal GridDataElementCls FindFirstCurrentItemByLocation(Point point)
		{
			List<GridDataElementCls> currentList = this.ElementVisibleList;
			foreach (GridDataElementCls item in currentList)
			{
				if (item.ElementArea.Contains(point))
					return item;
			}
			return null;
		}
		/// <summary>
		/// Metoda najde a vrátí seznam všech items z pole Current, které se nachází na dané vizuální lokalitě (dané konkrétním bodem).
		/// Souřadnice daného bodu musí odpovídat souřadnicím položek v poli this.ElementVisibleList.
		/// Pokud si nadřízená aplikace souřadnice prvků upravuje 
		/// (což pravděpodobně činí, protože zdejší souřadnice mají počátek = bod 0/0 v počátku řádku, 
		/// ale volající aplikace kreslí řádek do jiného počátku = řádek putuje po parent controlu),
		/// pak musí volající aplikace reverzním způsobem převést souřadnici bodu tak, aby byla vztažena k počátku 0/0 tohoto řádku.
		/// Pokud na daném bodě není žádný prvek, vrací prázdný list.
		/// </summary>
		/// <param name="point"></param>
		/// <returns></returns>
		internal List<GridDataElementCls> FindCurrentItemListByLocation(Point point)
		{
			List<GridDataElementCls> currentList = this.ElementVisibleList;
			List<GridDataElementCls> result = new List<GridDataElementCls>();
			foreach (GridDataElementCls item in currentList)
			{
				if (item.ElementArea.Contains(point))
					result.Add(item);
			}
			return result;
		}
		/// <summary>
		/// Metoda najde a vrátí nejbližší element, který se nachází před / za daným časovým intervalem.
		/// </summary>
		/// <param name="timeRange"></param>
		/// <param name="timeDir"></param>
		/// <returns></returns>
		internal GridDataElementCls FindNextElement(TimeRange timeRange, TimeRange.TimeDirection timeDir)
		{
			List<GridDataElementCls> elementList = this.ElementAllList;
			GridDataElementCls result = null;
			switch (timeDir)
			{
				case TimeRange.TimeDirection.ToHistory:
					foreach (GridDataElementCls element in elementList)
					{
						if (element.TimeRange.End < timeRange.Begin && (result == null || element.TimeRange.End > result.TimeRange.End))
							result = element;
					}
					break;
				case TimeRange.TimeDirection.ToFuture:
					foreach (GridDataElementCls element in elementList)
					{
						if (element.TimeRange.Begin > timeRange.End && (result == null || element.TimeRange.Begin < result.TimeRange.Begin))
							result = element;
					}
					break;
			}
			return result;
		}
        /// <summary>
        /// Tato metoda najde a vrátí všechny elementy, které se nacházejí v prostoru Select-framování.
        /// Tato metoda hledá elementy ve všech svých elementech, nejen v elementech momentálně viditelných.
        /// Tedy prohledá i elementy s nulovou vizuální délkou (takové se do grafu nedostanou, ale pro účely Select-Frame je nelze opomíjet.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="selectY"></param>
        /// <returns></returns>
        internal List<GridDataElementCls> FindElementsByFrame(TimeRange time, OneDimensionI selectY, float selectCross)
        {
            lock (this._ElementDict)
            {
                if (this._ElementAllListNeedPrepare)
                    this._ElementAllListPrepareLock();

                return this._FindElementsByFrameLock(time, selectY, selectCross);
            }
        }

        private List<GridDataElementCls> _FindElementsByFrameLock(TimeRange time, OneDimensionI selectY, float selectCross)
        {
            List<GridDataElementCls> result = new List<GridDataElementCls>();
            foreach (GridDataElementCls element in this._ElementAllList)
            {
                TimeRange elementTime = element.TimeRange;
                if (elementTime.End < time.Begin || elementTime.Begin > time.End) continue;
                if (element.ElementRelArea.Bottom < selectY.Begin || element.ElementRelArea.Y > selectY.End) continue;
                result.Add(element);
            }
            return result;
        }
		#endregion
		#region ROW FILTER
		/// <summary>
		/// true, pokud tento řádek má být vidět (řádkový filtr AND (needituje se nic, anebo právě tento řádek se edituje)).
		/// </summary>
		internal bool RowIsVisibleByFilter { get; set; }
		/// <summary>
		/// Vrátí true, když aktuální řádek vyhovuje danému filtru.
		/// </summary>
        /// <param name="rowFilter"></param>
        internal bool ConformToRowFilter(RowFilter rowFilter)
		{
            if (rowFilter == null) return true;

            // Explicitní programový filtr:
            if (!rowFilter.RowIsExplicitConform(this.GId)) return false;

            // Uživatelem zadaný filtr:
            foreach (RowFilterItem filterItem in rowFilter)
            {
                int index = filterItem.ColumnIndex;
                string key = filterItem.ItemName.ToLower();

                string value = this.RowItem.GetColumnValue(index, key);
                if (value == null)
                    value = this.RowItem.GetColumnContent(index, key);
                if (value != null)
				{
                    if (!filterItem.ValueIsConform(value))
						// Pokud tomuto filtru (položka) naše hodnota Refer nevyhovuje, pak vrátí false (řádek nevyhovuje filtru).
						return false;
				}
            }
            return true;
		}
		#endregion
		#region SUBCLASS _MultiThreadGroup : skupina elementů se stejnou hodnotou ElementGroup.
		/// <summary>
		/// _MultiThreadGroup : skupina elementů se stejnou hodnotou ElementGroup. 
		/// Slouží při uspořádávání elementů typu MultiThread do grupovaných bloků řádku.
		/// </summary>
		private class _MultiThreadGroup
		{
			private _MultiThreadGroup(int group)
			{
				this.Group = group;
				this.GroupElementList = new List<GridDataElementCls>();
			}
			internal int Group;
			internal List<GridDataElementCls> GroupElementList;
			/// <summary>
			/// Ze seznamu elementů vrátí seznam skupin, kde jsou elementy rozčleněny do skupin dle ElementGroup.
			/// Seznam skupin vrací setříděný dle čísla ElementGroup vzestupně.
			/// </summary>
			/// <param name="elements"></param>
			/// <returns></returns>
			internal static List<_MultiThreadGroup> CreateGroups(IEnumerable<GridDataElementCls> elements)
			{
				// Vytvořím sadu objektů, členěnou podle čísla grupy z prostého seznamu elementů:
				Dictionary<int, _MultiThreadGroup> groups = new Dictionary<int, _MultiThreadGroup>();
				foreach (GridDataElementCls element in elements)
				{
					_MultiThreadGroup mtg;
					if (!groups.TryGetValue(element.ElementGroup, out mtg))
					{
						mtg = new _MultiThreadGroup(element.ElementGroup);
						groups.Add(element.ElementGroup, mtg);
					}
					mtg.GroupElementList.Add(element);
				}

				// Vytvořím seznam a setřídím jej podle čísla grupy:
				List<_MultiThreadGroup> result = new List<_MultiThreadGroup>(groups.Values);
				result.Sort(_MultiThreadGroup.CompareByGroup);
				return result;
			}
			/// <summary>
			/// Metoda připraví elementy jedné (svojí) grupy pro zobrazení: určí jejich vertikální souřadnice.
			/// Vrátí takto připravené elementy.
			/// </summary>
			/// <param name="args"></param>
			/// <returns></returns>
			internal IEnumerable<GridDataElementCls> PrepareElements(_MultiThreadPrepareArgs args)
			{
				List<GridDataElementCls> result = new List<GridDataElementCls>();

				// Vstupující elementy (jsou vyfiltrované jen pro jednu grupu) setřídit dle data počátku / a konce:
				if (this.GroupElementList.Count > 1)
					this.GroupElementList.Sort(GridDataElementCls.CompareByBegin);

				// Pracovní seznam s informacemi o nyní přítomných položkách ve vizuálním seznamu, v okolí aktuálního času (tj. data.Begin):
				// Obsahuje položky = předchozí elementy (předchozí dle času jejich Begin), a které trvají přinejmenším v čase Begin tohoto elementu.
				// Popisuje jejich vertikální pozici v grafu, v relativních hodnotách (kde 1 = jedna výrobní linka = stroj),
				// a pomáhá tak určovat relativní pozici pro nově umisťovaný element = tak, aby se vešel mezi stávající elementy, anebo se umístil nad ně.
				// Skládá tak elementy na sebe jako v Tetrisu, s tím že element může propadnout i do nižšího "řádku", pokud se tam celý vejde
				//  (což je prioritní postup).
				List<_UseItem> useList = new List<_UseItem>();

                // Poznámka pro elementy se šířkou 0 (čas End = Begin) ("E0"):
                // a) takový element E0 se vejde za takový předchozí element, který končí v témže čase, jako je E0.Begin
                // b) za elementem E0 může bezprostředně začínat další element, který začíná v čase == E0.End
                // c) výjimku tvoří sousední elementy s délkou 0:
                //   c1) pokud element E0 začíná v čase, v němž končí (tj. i začíná) jiný element s délkou 0, pak se nevejde a dává se na výšku
                //   c2) pokud za elementem E0 začíná další element s délkou 0 ve shodném čase, opět se nevejde a dává se na výšku

				// Pro každý element (bez ohledu na jejich viditelnost v aktuální časové ose):
				foreach (GridDataElementCls element in this.GroupElementList)
				{
					// V tuto chvíli není žádný element viditelný (na konci této metody se do this._CurrentVisualItemList zapisuje null):
					// Viditelnost elementů se řeší v metodě this._CurrentVisualItemListPrepareLock(), tam se budou setovat viditelné elementy.
					element.SetVisibleAxis(false);
                    // Žádný element nemá nastavený časový offset, ten se nastaví až při určení pozice elementu o pár řádků později:
					element.BeginPlus02 = false;
					element.EndMinus02 = false;

					// Určíme pozice na souřadnici Y pro tento element:
					decimal relativeBottom = 0M;
					decimal relativeTop = 1M;
					decimal relativeHeight = 1M;

					if (!element.IsTemporary && element.IsItemLayer)
					{	// Element v řádku MultiThread: zde se používá dělení podle UseRatio, třídím položky nad sebe jako v Tetrisu:
						// Klíčový okamžik je Begin tohoto elementu:
						SubTimeRange elementSubTime = new SubTimeRange(element.TimeRange);
						if (elementSubTime.Sign < 0) continue;

						// Z pracovního seznamu vyhodím všechny prvky, které mají konec v nebo před datem Begin:
						// Poznámka: useList pracuje s časem typu SubTimeRange, tedy pod 1 milisekundu, 
						//           kde elementy s kladnou délkou jsou mírně zmenšené (dovnitř, na každé straně o 0.2 milisec),
                        //           a elementy s nulovou délkou jsou mírně zvětšené (ven, na každé straně o 0.2 milisec), takže navzájem si nepřekážejí,
						//           ale dva elementy s nulovou délkou se musí skládat nad sebe (protože si překážejí).
                        // V tomto chodu chci najít i těsně sousedící elementy (kde End předešlého == Begin následujícího, kdy jeden z nich je nulový),
                        //  proto v seznamu useList ponechám ty elementy, kde "u.SubTime.End == elementSubTime.Begin):
                        useList.RemoveAll(u => u.SubTime.End < elementSubTime.Begin);

						// Nyní mám v seznamu useList jen ty prvky, které začaly před naším prvkem (pořadí zpracování je Begin ASC), 
						//   a které končí v okamžiku začátku anebo za začátkem našeho prvku.
						// Tedy jde o dotykové nebo konfliktní prvky.
                        // Z hlediska třídění elementů s nulovou délkou v seznamu this.GroupElementList:
						//   Ve stejném čase TimeRange.Begin jsou nejprve elementy s nulovou délkou = ty, které mají SubTimeRange.Begin menší
						//   (to zajišťuje třídička GridDataElementCls.CompareByBegin). Nejprve tedy umístím nulové elementy, 
						//   a za ně pak (do stejného času Begin) umístím kladné elementy.

						// Zde najdu (odspodu) takový element v seznamu useList (prohledávám jej odspodu), 
						//  pod který by se na výšku vešel náš element:
						relativeHeight = 1M /* data.UseRatio */; // Potřebný prostor pro náš element (zde se UseRatio nepoužívá !)
						int insertAt = -1;                       // Index prvku v seznamu useList, před který vložím náš element.
                        GridDataElementCls elementLeft = null;   // Element, který je těsně vlevo před námi = mají dotyk
						int useCount = useList.Count;
						if (useCount > 0)
						{	// Pokud máme nějaký (alespoň jeden) prvek, musím vyřešit, kam by bylo možno vložit (insertovat) aktuální prvek v tomto seznamu = na kterou pozici Bottom:
							decimal lastTop = args.CurrentGroupBottom;   // Na jaké souřadnici (Bottom) začíná aktuální skupina (this)? (po dokončení jedné skupiny se navýší)
							for (int u = 0; u < useCount; u++)
							{
                                _UseItem uItem = useList[u];

                                decimal freeY = uItem.Bottom;            // Souřadnice Y (relativní), pod kterou je volný prostor

                                // Registrujeme dotyk:
                                if (uItem.SubTime.End == elementSubTime.Begin)
                                {   // Pokud předchozí element končí přesně v čase našeho začátku = pak elementy mají dotyk:
                                    elementLeft = uItem.Element;         // Zapamatuji si element, jehož se vlevo dotýkám. 
                                    freeY = uItem.Top;                   // Tento element nám nepřekáží => volný prostor je daný souřadnicí Top tohoto elementu
                                }

                                // Pokud se pod souřadnici freeY vejdeme, pak je vyřešeno:
                                if ((freeY - lastTop) >= relativeHeight)
								{	// Pokud pod tímto prvkem (jeho Bottom) je dolů směrem k lastTop tolik místa, aby se tam vešel aktuální prvek:
									insertAt = u;                        // Index, na který vložíme do pracovního seznamu useList záznam
									relativeBottom = lastTop;
									break;
								}

                                if (uItem.SubTime.End > elementSubTime.Begin)
                                {   // Pokud tento prvek fyzicky končí později, než náš element začíná, pak nám skutečně překáží a my musíme hledat volné místo někde nad ním:
                                    // Pamatuji si: počátek volného prostoru (bottom pro příští prvek) 
                                    //  by mohl být na vrcholku současného existujícího prvku:
                                    lastTop = useList[u].Top;
                                    elementLeft = null;
                                }
							}
							// Pokud jsem nenašel žádné volné místo MEZI prvky, pak nový prvek umístím NAD poslední prvek:
                            if (insertAt < 0)
                            {
                                relativeBottom = lastTop;
                                elementLeft = null;
                            }
						}
						else
							relativeBottom = args.CurrentGroupBottom;

						// Pracovní seznam: do něj vložíme/přidáme záznam za tento datový prvek:
						relativeTop = relativeBottom + relativeHeight;
						_UseItem useItem = new _UseItem(element, elementSubTime, relativeBottom, relativeTop);
						if (insertAt < 0)
							useList.Add(useItem);
						else
							useList.Insert(insertAt, useItem);

                        // Zajistím registraci dotyku dvou elementů:
                        if (elementLeft != null)
                        {
                            if (elementSubTime.Sign == 0)
                                // Pokud já jsem nulový, tak se budu zobrazovat zvětšený, a měl bych zmenšit toho, kdo je vlevo (jeho konec):
                                elementLeft.EndMinus02 = true;
                            else
                                // Pokud já jsem kladný, tak se mohu vlevo dotýkat jen nulového (kladný mi nepřekáží), takže zmenším sebe (svůj začátek):
                                element.BeginPlus02 = true;
                        }

						// Střádám si hodnoty (sumMax a oneMin):
						if (useItem.Top > args.UseRatioSumMax) args.UseRatioSumMax = useItem.Top;
						if (relativeHeight > 0M && (args.UseRatioOneMin == 0M || relativeHeight < args.UseRatioOneMin)) args.UseRatioOneMin = relativeHeight;
						if (!args.ContainFragments && relativeHeight != 1) args.ContainFragments = true;      // obsahuje zlomky?
					}

					element.StoreRelativeY(relativeBottom, relativeTop);
					result.Add(element);
				}

				// Pro případnou příští grupu nastavím její Bottom:
				args.CurrentGroupBottom = args.UseRatioSumMax;

				return result;
			}
			/// <summary>
			/// Komparátor dle čísla grupy
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			internal static int CompareByGroup(_MultiThreadGroup a, _MultiThreadGroup b)
			{
				return a.Group.CompareTo(b.Group);
			}
		}
		#endregion
		#region SUBCLASS _MultiThreadPrepareArgs : data pro přípravu grafu MutliThreads (sada různých proměnných pro přípravu výškového uspořádání)
		/// <summary>
		/// _MultiThreadPrepareArgs : data pro přípravu grafu MutliThreads (sada různých proměnných pro přípravu výškového uspořádání)
		/// </summary>
		private class _MultiThreadPrepareArgs
		{
			internal _MultiThreadPrepareArgs()
			{
				this.UseRatioSumMax = 0M;
				this.UseRatioOneMin = 0M;
				this.ContainFragments = false;
				this.CurrentGroupBottom = 0M;
			}
			/// <summary>
			/// Nalezené maximum výšky
			/// </summary>
			internal decimal UseRatioSumMax;
			/// <summary>
			/// Nejmenší nalezený zlomek výšky
			/// </summary>
			internal decimal UseRatioOneMin;
			/// <summary>
			/// Příznak, že řádek obsahuje zlomky (kde UseRatio < 1)
			/// </summary>
			internal bool ContainFragments;
			/// <summary>
			/// Spodní souřadnice aktuální grupy, po dokončení každé grupy se nastaví na její maxTop
			/// </summary>
			internal decimal CurrentGroupBottom;
		}
		#endregion
		#region SUBCLASS _UseItem : slouží při přepočtech pozice Bottom při skládání elementů do vizuálního řádku
		/// <summary>
		/// _UseItem : slouží při přepočtech pozice Bottom při skládání elementů do vizuálního řádku
		/// </summary>
		private class _UseItem
		{
			internal _UseItem(GridDataElementCls element, TimeRange time, decimal bottom, decimal top)
			{
                this.Element = element;
				this.SubTime = new SubTimeRange(time);
				this.Bottom = bottom;
				this.Top = top;
			}
			internal _UseItem(GridDataElementCls element, SubTimeRange time, decimal bottom, decimal top)
			{
                this.Element = element;
                this.SubTime = time;
				this.Bottom = bottom;
				this.Top = top;
			}
			public override string ToString()
			{
				return "Time=" + this.SubTime.ToString() + "; Bottom=" + this.Bottom.ToString() + "; Top=" + this.Top.ToString();
			}
			internal GridDataElementCls Element { get; private set; }
            /// <summary>
            /// Čas z elementu, standardní TimeRange s akceptovanou přesností na milisekundy, bez úprav času !!!
            /// </summary>
            internal TimeRange Time { get { return this.Element.TimeRange; } }
            /// <summary>
            /// Čas SubTimeRange, odvozený původně z elementu, pro nulový element je mírně zvětšený (o 2x 0.2 milisec), pro kladný element je mírně zmenšený.
            /// Důvodem je umožnit vložení jednoho nulového elementu mezi dva nenulové elementy, ale přitom skládání elementů nulových nad sebe.
            /// </summary>
            internal SubTimeRange SubTime { get; private set; }
            /// <summary>
            /// Souřadnice Bottom tohoto elementu, relativní (1 = výška jednoho elementu s UseRatio = 1M), zde nejde o pixely.
            /// Hodnota 0 je docela dole.
            /// </summary>
			internal decimal Bottom { get; private set; }
            /// <summary>
            /// Souřadnice Top tohoto elementu, relativní (1 = výška jednoho elementu s UseRatio = 1M), zde nejde o pixely.
            /// Hodnota 0 je docela dole.
            /// </summary>
			internal decimal Top { get; private set; }
		}
		#endregion
    }
	#endregion
    #region CLASS GridDataElementCls : obálka nad daty elementu, slouží pro ukládání elementu do řádku a pro vizuální přepočty nad tímto elementem.
    /// <summary>
    /// GridDataElementCls : obálka nad daty elementu, slouží pro ukládání elementu do řádku a pro vizuální přepočty nad tímto elementem.
    /// </summary>
    internal class GridDataElementCls
    {
        #region Konstrukce a základní property (řádek, element, pointer)
        internal GridDataElementCls(GridDataRowCls parent, IDataElement dataElement)
		{
			this.Parent = parent;
            this.DataElement = dataElement;
            this.TempElementState = (dataElement.ElementLayer != GraphLayerType.ElementTempLayer ? GraphTempElementState.Standard : GraphTempElementState.TempWork);
        }
		/// <summary>
		/// Reference na parent objekt = vizuální řádek, kde je tento element evidován.
		/// Každý element je hostován v jednom logickém řádku (třídy GridDataRowCls),
		/// ale jeden řádek (GridDataRowCls) může být zobrazen ve více nodech (třída GridNodeRowCls).
		/// </summary>
		internal GridDataRowCls Parent { get; set; }
        /// <summary>
        /// Logická data o grafickém elementu.
        /// Jde o data, která vrátila datová vrstva.
        /// Obsahují zejména časový interval, typ údaje, specifikaci grafu, režim pohybu a další.
        /// </summary>
        internal IDataElement DataElement { get; private set; }
        /// <summary>
        /// Pointer na element = jeho logická adresa (řádek, element).
        /// </summary>
        internal DataPointerStr DataPointer { get { return new DataPointerStr(this.Parent.GId, this.DataElement.GId); } }
        /// <summary>
        /// Text pro kontrolu logické a fyzické pozice
        /// </summary>
        internal string DebugInfo
        {
            get
            {
                string eol = Environment.NewLine;
                string info = "ID=" + this.GId + "; Time=" + this.TimeRange + "; UseRatio=" + "N/A" + eol +
                    "Bottom=" + this.RelativeBottom + "; Top=" + this.RelativeTop + eol +
                    "ElementArea: {Left=" + this.ElementArea.Left + ",Width=" + this.ElementArea.Width + ",Top=" + this.ElementArea.Top + ", Height=" + this.ElementArea.Height + ", Bottom=" + this.ElementArea.Bottom + "}";
                return info;
            }
        }
        public override string ToString()
        {
            return "Element={" + this.DataElement.ToString() + "}; Bottom=" + this.RelativeBottom.ToString() + "; Top=" + this.RelativeTop.ToString();
        }
        #endregion
        #region Vizuální property, zdejší data
        /// <summary>
        /// Prostor elementu přes celou výšku řádku.
        /// Bod 0,0 je levý horní roh prostoru grafu tohoto řádku (Left = začátek časové osy, Top = začátek tohoto řádku).
        /// </summary>
        internal Rectangle ElementRowArea;
        /// <summary>
        /// Prostor elementu přes relativní souřadnice Y (relativní = vzhledem k řádku).
        /// Zde jsou již korektně určeny vizuální linky.
        /// </summary>
        internal Rectangle ElementRelArea;
        /// <summary>
        /// Obsahuje (vrátí) true, pokud tento element potřebuje zvětšit svoji šířku (tj. když má kladný anebo nulový čas, ale nemá Image a jeho současná šířka ElementRelArea.Width je menší než 3 pixely
        /// </summary>
        internal bool NeedAddVisualWidth { get { return (this.TimeRange.Seconds >= 0D && !this.HasImage && this.ElementRelArea.Width < 3); } }
        /// <summary>
        /// Obsahuje (vrátí) true, pokud tento element nelze zobrazit (jeho čas je záporný)
        /// </summary>
        internal bool TimeInvisible { get { return (this.TimeRange.Seconds < 0D); } }
        /// <summary>
        /// Hodnota RelativeBottom, relativní (hodnota 1 = jedna celá kapacitní linka), nejde o pixely.
        /// Hodnota RelativeTop je vyšší než RelativeBottom.
        /// První linka dole má hodnotu RelativeBottom = 0, a RelativeTop rovnou jejímu UseRatio.
        /// </summary>
        internal decimal RelativeBottom { get; private set; }
        /// <summary>
        /// Hodnota RelativeTop, relativní (hodnota 1 = jedna celá kapacitní linka), nejde o pixely.
        /// Hodnota RelativeTop je vyšší než RelativeBottom.
        /// První linka dole má hodnotu RelativeBottom = 0, a RelativeTop rovnou jejímu UseRatio.
        /// </summary>
        internal decimal RelativeTop { get; private set; }
        /// <summary>
        /// Hodnota RelativeHeight, relativní (hodnota 1 = jedna celá kapacitní linka), nejde o pixely.
        /// Jde o rozdíl RelativeTop - RelativeBottom.
        /// </summary>
        internal decimal RelativeHeight { get { return this.RelativeTop - this.RelativeBottom; } }
        /// <summary>
        /// Pozice elementu grafu v rámci aktuálního grafu řádku.
        /// Souřadnice X je relativní k aktuálnímu počátku grafu podle aktuálního počátku časové osy. Odpovídá času Begin. Může být záporná.
        /// Dimenze Width odpovídá době trvání elementu.
        /// Souřadnice Y je 1.
        /// Dimenze Height je dána výškou řádku - 2 (řeší se pouze GridLines).
        /// Tento údaj se aktualizuje pouze při posunech logických dat a při posunech časové osy a jejího měřítka.
        /// Při scrollování grafu nahoru/dolů se nemění.
        /// Při posouvání času doleva/doprava nebo změně časového měřítka se objekty přepočítávají.
        /// </summary>
        internal Rectangle ElementArea { get; set; }
        /// <summary>
        /// Pozice detailu grafu = může být podmnožinou prostoru ElementArea.
        /// Typicky se využívá v grafu typu MultiTask pro zobrazení elementů typu TaskLink, v rámci konkrétní linky,
        /// viz příprava v metodě this._ChartElementsPrepareVisual().
        /// </summary>
        internal Rectangle DetailArea { get; set; }
        /// <summary>
        /// Zobrazovaná pozice sub položky grafu Back (vykreslované pozadí). 
        /// Data jsou plněna jen pro určité typy grafů, viz metoda this.PrepareVisualDataForGraph() a návazné.
        /// Platné údaje jsou X, Width, Y, Height. 
        /// Pozice X a Y jsou relativní k pozici X,Y buňky, v níž se graf vykresluje.
        /// Rozměry Width a Height jsou v pixelech.
        /// Tento údaj se aktualizuje pouze při posunech logických dat a při posunech časové osy a jejího měřítka.
        /// Při scrollování grafu se nemění.
        /// </summary>
        internal Rectangle RectangleBack { get; set; }
        /// <summary>
        /// Vrchol použité kapacity, nejvyšší souřadnice Y
        /// </summary>
        internal int CapacityUsedTop { get; set; }
        /// <summary>
        /// Aktuální platná vizuální pozice položky grafu.
        /// Souřadnice jsou uvedeny v koordinátech Controlu, v němž je element vykreslen.
        /// Prostor je oříznutý do rozměrů aktuálního grafu (na rozdíl od ElementArea, který definuje pozici elementu v logickém prostoru grafu).
        /// Tento údaj se aktualizuje při každém vykreslení grafu.
        /// </summary>
        internal Rectangle VisualRectangle { get; set; }
        /// <summary>
        /// Stav temp elementu (Standard = běžný element; TempWork = Temp element, zpracovává se; TempError = Temp element s chybou)
        /// </summary>
        internal GraphTempElementState TempElementState { get; set; }
        /// <summary>
        /// Tooltip nastavený grafem. Typicky obsahuje text s chybou.
        /// </summary>
        internal string ToolTipGraph { get; set; }
        /// <summary>
        /// Tooltip nastavený grafem. Typicky obsahuje rozsáhlejší text o chybě.
        /// </summary>
        internal string ToolTipGraphExtended { get; set; }
        #endregion
		#region Příznaky (bool), ukládané souhrnně do jednoho Int32
        /// <summary>
        /// Příznak true, když se element nachází ve viditelné části časové osy.
        /// Hodnota je určena při přípravě seznamu viditelných elementů.
        /// </summary>
		internal bool IsInVisibleAxis { get { return _GetBool(0); } private set { _SetBool(0, value); } }
        /// <summary>
        /// Příznak true, když jsou vypočteny všechny vizuální dimenze elementu.
        /// Hodnota je false pro elementy, které nejsou viditelné, a nebyly dosud kalkulované.
        /// Viditelné elementy jsou kalkulované automaticky, 
        /// neviditelné jsou kalkulovány při požadavku na nalezení a vydání takového elementu (viz metoda GridDataRowCls.GetChartElement()).
        /// </summary>
		internal bool IsAllDimensionCalculated { get { return _GetBool(1); } private set { _SetBool(1, value); } }
        /// <summary>
        /// Příznak, že tento prvek grafu je nyní vhodné zobrazit.
        /// </summary>
		internal bool Visible { get { return _GetBool(2); } set { _SetBool(2, value); } }
		/// <summary>
		/// Příznak, že Begin tohoto elementu se má zvýšit o 0.2 milisec, 
		/// protože před ním je element o nulové délce, který chceme zobrazit před ním s šířkou 0 + 2× 0.2 milisec
		/// </summary>
		internal bool BeginPlus02 { get { return _GetBool(3); } set { _SetBool(3, value); } }
        /// <summary>
        /// Offset času Begin (jemný posun):
        /// Pokud this je nulový čas, pak vrací -0.2 milisec.
        /// Pokud je nastaveno this.BeginPlus03 == true, pak vrací +0.2 milisec.
        /// Jinak vrací 0D.
        /// </summary>
        internal double BeginOffsetMilisec { get { return (this.TimeRange.Seconds == 0D ? -0.2D : (this.BeginPlus02 ? 0.2D : 0D)); } }
		/// <summary>
		/// Příznak, že End tohoto elementu se má snížit o 0.2 milisec, 
		/// protože za ním je element o nulové délce, který chceme zobrazit za ním s šířkou 0 + 2× 0.2 milisec
		/// </summary>
		internal bool EndMinus02 { get { return _GetBool(4); } set { _SetBool(4, value); } }
        /// <summary>
        /// Offset času End (jemný posun):
        /// Pokud this je nulový čas, pak vrací +0.2 milisec.
        /// Pokud je nastaveno this.EndMinus03 == true, pak vrací -0.2 milisec.
        /// Jinak vrací 0D.
        /// </summary>
        internal double EndOffsetMilisec { get { return (this.TimeRange.Seconds == 0D ? 0.2D : (this.EndMinus02 ? -0.2D : 0D)); } }
        /// <summary>
		/// Nastaví do daného bitu hodnotu value (true = 1 / nebo false = 0)
		/// </summary>
		/// <param name="bit">Bit 0 - 30</param>
		/// <param name="value"></param>
		private void _SetBool(int bit, bool value)
		{
			int mask = 1 << bit;
			if (value)
				_BoolValues = _BoolValues | mask;
			else
				_BoolValues = _BoolValues & (mask ^ int.MaxValue);
		}
		/// <summary>
		/// Vrátí true, pokud daný bit je 1
		/// </summary>
		/// <param name="bit">Bit 0 - 30</param>
		/// <returns></returns>
		private bool _GetBool(int bit)
		{
			return ((_BoolValues & (1 << bit)) > 0);
		}
		private int _BoolValues;
		#endregion
        #region Property dotáhnuté z IDataElement
        /// <summary>
        /// Aktuální hodnota GID tohoto elementu. Je neměnná.
        /// </summary>
        internal GID GId { get { return this.DataElement.GId; } }
        /// <summary>
        /// Aktuální hodnota TimeRange elementu
        /// </summary>
        internal TimeRange TimeRange { get { return this.DataElement.TimeRange; } set { this.DataElement.TimeRange = value; } }
        /// <summary>
        /// Příznak, že tento element není "časový" ale "celořádkový".
        /// Praktické použití je jen u Temp elementu při načítání podřízených řádků, kdy element nereprezentuje čas, ale obsazení celého řádku.
        /// </summary>
        internal bool FullRow { get { return this.DataElement.FullRow; } }
        /// <summary>
        /// Příznak, že tento element má Image, a nemusí být vykreslován jen jako Rectangle
        /// </summary>
        internal bool HasImage { get { return (this.DataElement.ElementImage != null); } }
        /// <summary>
        /// Příznak, že tento element má nulový TimeRange, a současně má Image, bude tedy kreslen jen jako Image - ale nesmí být vypuštěn z kreslení
        /// </summary>
        internal bool HasOnlyImage { get { return (this.TimeRange.Seconds < 0D && this.DataElement.ElementImage != null); } }
        /// <summary>
        /// Zlomky elementu, převezme si je vhodný painter
        /// </summary>
        internal float[] Fragments { get { return this.DataElement.Fragments; } }
        /// <summary>
        /// Příslušnost data do vrstvy.
        /// Odlišuje vrstvu podkladovou (typicky zobrazení směny) a vrstvu datovou (aktivita v čase, která může být delší / kratší než směna).
        /// </summary>
        internal GraphLayerType ElementLayer { get { return this.DataElement.ElementLayer; } }
        /// <summary>
        /// Příslušnost elementu do skupiny.
        /// V grafu typu MultiThread jsou elementy skládány "nad sebe", a jsou členěny do skupin (Groups).
        /// Elementy jedné skupiny jsou ve své "řádce", a nemíchají se do řádky jiných skupin.
        /// Implicitní hodnota je 0.
        /// </summary>
        internal int ElementGroup { get { return this.DataElement.ElementGroup; } }
        /// <summary>
        /// Tvar elementu.
        /// </summary>
        internal GraphElementShapeType ElementShapeType { get { return this.DataElement.ElementShapeType; } }
        /// <summary>
        /// Vlastnosti elementu
        /// </summary>
        internal ElementProperties Properties { get { return this.DataElement.Properties; } }
        /// <summary>
        /// Barva pozadí elementu
        /// </summary>
        internal Color ElementBackColor { get { return this.DataElement.BackColor; } }
        /// <summary>
        /// Barva písma v elementu
        /// </summary>
        internal Color ElementFontColor { get { return this.DataElement.FontColor; } }
        /// <summary>
        /// Vrací tooltip, buď zdejší (this.ToolTipGraph), anebo pro něj sáhne do elementu metodou this.DataElement.ToolTipInfo(iDataRow);.
        /// </summary>
        /// <param name="iDataRow"></param>
        /// <returns></returns>
        internal string ToolTipInfo(IDataRow iDataRow)
        {
            if (!String.IsNullOrEmpty(this.ToolTipGraphExtended))
                return this.ToolTipGraphExtended;
            string toolTip = this.DataElement.ToolTipInfo;
            if (toolTip == null)
                toolTip = this.DataElement.GetToolTipInfo(iDataRow);
            return toolTip;
        }
        /// <summary>
        /// Příznak, že Caption (text v elementu) má být zobrazen vždy když se vejde, nejen pod myší.
        /// </summary>
        internal bool CaptionShowAllways { get { return this.DataElement.CaptionShowAllways; } }
        /// <summary>
        /// Aktuální hodnota GID řádku
        /// </summary>
        internal GID RowGid { get { return this.Parent.GId; } }
        #endregion
        #region Metody a property (ne data) pro podporu vizuálních kalkulací
        /// <summary>
        /// For internal use only!!!
        /// Set value to property "IsInVisibleAxis".
        /// </summary>
        /// <param name="value"></param>
        internal void SetVisibleAxis(bool value) { this.IsInVisibleAxis = value; }
        /// <summary>
        /// Vloží dané dimenze do svého Rectangle ElementRowArea = prostor přes celý řádek
        /// </summary>
        /// <param name="dimX"></param>
        /// <param name="dimY"></param>
        internal void StoreElementRowArea(OneDimensionI dimX, OneDimensionI dimY)
        {
            this.ElementRowArea = new Rectangle(dimX.Begin, dimY.Begin, dimX.Length, dimY.Length);
            this.IsAllDimensionCalculated = false;
        }
        /// <summary>
        /// Vloží dané dimenze do svého Rectangle ElementRelArea = prostor podle relativních souřadnic
        /// </summary>
        /// <param name="dimX"></param>
        /// <param name="dimY"></param>
        internal void StoreElementRelArea(OneDimensionI dimX, OneDimensionI dimY)
        {
            this.ElementRelArea = new Rectangle(dimX.Begin, dimY.Begin, dimX.Length, dimY.Length);
            this.IsAllDimensionCalculated = false;
        }
        /// <summary>
        /// Uloží do objektu jeho relativní pozici na ose Y (Bottom, Top).
        /// Souřadnice se uvádí v relativní hodnotě 0.00 - 1.00
        /// </summary>
        /// <param name="bottom"></param>
        /// <param name="top"></param>
        internal void StoreRelativeY(decimal bottom, decimal top)
        {
            this.RelativeBottom = bottom;
            this.RelativeTop = top;
        }
        /// <summary>
        /// Zajistí kontrolu, zda jsou vypočteny interní dimenze elementu.
        /// Pokud jsou, nic nedělá.
        /// Pokud nejsou, vypočte je.
        /// </summary>
        internal void InternalDimensionCheck()
        {
            if (!this.IsAllDimensionCalculated)
                this.InternalDimensionCalculate();
        }
        /// <summary>
        /// Zajistí vypočtení vnitřních dimenzí elementu na základě jeho typu.
        /// </summary>
        internal void InternalDimensionCalculate()
        {
            if (this.DataElement.ElementLayer == GraphLayerType.ElementTempLayer)
                this._PrepareVisualDataForTemp();
            else
            {
                switch (this.Parent.RowGraph)
                {
                    case RowGraphMode.SingleTask:
                        this._PrepareVisualDataForSingleTask();
                        break;
                    case RowGraphMode.Percentage:
                        this._PrepareVisualDataForPercentage();
                        break;
                    case RowGraphMode.MultiTask:
                        this._PrepareVisualDataForMultiTask();
                        break;
                    case RowGraphMode.BarGraph:
                        this._PrepareVisualDataForBarGraph();
                        break;
                    default:
                        throw new NotImplementedException("Metoda InternalDimensionCalculate() není implementována pro graf " + this.Parent.RowGraph.ToString());
                }
            }

            // Vypočteno:
            this.IsAllDimensionCalculated = true;
        }
        /// <summary>
        /// Připraví dimenze elementu pro element vrstvy Temporary.
        /// Takový element se vykreslí přes celou výšku řádku.
        /// </summary>
        private void _PrepareVisualDataForTemp()
        {
            // Element je připraven, vykreslí se plná velikost elementu z this.ElementArea, nic víc není zapotřebí:
            this.ElementArea = this.ElementRowArea;
        }
        /// <summary>
        /// Připraví dimenze elementu pro graf typu Gantt.
        /// V tuto chvíli je připravena hodnota do this.ElementArea, popisuje celý vnitřní prostor řádku (Y, height) a času (X, Width).
        /// </summary>
        private void _PrepareVisualDataForSingleTask()
        {
            // Gantt je připraven, vykreslí se plná velikost elementu z this.ElementArea, nic víc není zapotřebí:
            this.ElementArea = this.ElementRowArea;
        }
        /// <summary>
        /// Připraví dimenze elementu pro graf typu CapacityUtilization.
        /// V tuto chvíli je připravena hodnota do this.ElementArea, popisuje celý vnitřní prostor řádku (Y, height) a času (X, Width).
        /// </summary>
        private void _PrepareVisualDataForPercentage()
        {
            this.ElementArea = this.ElementRowArea;           // Prostor na výšku celého řádku.

            float standardUsage = 0.75F;

            int x = this.ElementArea.X;
            int w = this.ElementArea.Width;
            int b = this.ElementArea.Bottom;

            // a) pozadí kapacity :
            float standardHeight = standardUsage * (float)this.ElementArea.Height;         // Výška elementu, který zobrazuje 100%
            int height = (int)standardHeight;
            this.RectangleBack = new Rectangle(x, b - height, w, height);

            // b) ratio:
            float utilizationHeight = 1F /*this.DataElement.UtilizationRatio*/ * standardHeight;  // Výška elementu, který zobrazuje (UtilizationRatio)%
            height = (int)utilizationHeight;
            int y = b - height;
            this.ElementArea = new Rectangle(x, y, w, height);

            // c) top souřadnice vrcholku UtilizationRatio, relativně k this.ElementArea
            this.CapacityUsedTop = y;
        }
        /// <summary>
        /// Připraví dimenze elementu pro graf typu MultiTask.
        /// V tuto chvíli je připravena hodnota do this.ElementArea, popisuje celý vnitřní prostor řádku (Y, height) a času (X, Width).
        /// </summary>
        private void _PrepareVisualDataForMultiTask()
        {
            // Tento graf zahrnuje dva typy objektů, odlišené hodnotou this.DataElement.ElementLayer :
            switch (this.DataElement.ElementLayer)
            {
                case GraphLayerType.ElementSubLayer:
                    // Stav kapacity - vykresluje se jako poloprůhledné pozadí grafu, 
                    //  v metodě GridGraphCls._PaintRowGraphCapacityBase, z dat v RectangleBack:
                    this.ElementArea = this.ElementRowArea;                   // Prostor na výšku celého řádku.
                    this.RectangleBack = this.ElementRowArea;
                    break;
                case GraphLayerType.ElementItemLayer:
                    // Vlastní položka grafu - vykresluje se v prostoru relativních souřadnic:
                    this.ElementArea = this.ElementRelArea;              // Relativní výška elementu
                    break;
            }
        }
        /// <summary>
        /// Připraví dimenze elementu pro graf typu BarGraph.
        /// V tuto chvíli je připravena hodnota do this.ElementArea, popisuje celý vnitřní prostor řádku (Y, height) a času (X, Width).
        /// </summary>
        private void _PrepareVisualDataForBarGraph()
        {
            this.ElementArea = this.ElementRowArea;           // Prostor na výšku celého řádku.

            // Základní souřadnice:
            int x = this.ElementArea.X;
            int w = this.ElementArea.Width;
            int b = this.ElementArea.Bottom;

            // Pozadí = prostor pro barvu čerpané paměti:
			float lineHeight = (float)this.Parent.LineHeightCurrPx;
			int h = (int)(Math.Round(this.DataElement.ElementHeightRatio * lineHeight, 0));
            int y = b - h;
            this.ElementArea = new Rectangle(x, y, w, h);







            //float standardUsage = 0.75F;


            //// a) pozadí kapacity :
            //float standardHeight = standardUsage * (float)this.ElementArea.Height;         // Výška elementu, který zobrazuje 100%
            //int height = (int)standardHeight;
            //this.RectangleBack = new Rectangle(x, b - height, w, height);

            //// b) ratio:
            //float utilizationHeight = 1F /*this.DataElement.UtilizationRatio*/ * standardHeight;  // Výška elementu, který zobrazuje (UtilizationRatio)%
            //height = (int)utilizationHeight;
            //int y = b - height;
            //this.ElementArea = new Rectangle(x, y, w, height);

            // c) top souřadnice vrcholku UtilizationRatio, relativně k this.ElementArea
            this.CapacityUsedTop = y;
        }
        
        /// <summary>
        /// Vrátí true, pokud se element má vložit do seznamu viditelných elementů, v závislosti na šířce.
        /// Element spadá do viditelného časového intervalu.
        /// Pokud je jeho šířka menší než (???) nebo rovna nule, pak se jeho přidávání řídí jinými pravidly.
        /// Aktuální dimenze jsou uvedeny v this.ElementRowArea a this.ElementRelArea.
        /// Dimenze X jsou v obou stejné.
        /// </summary>
        /// <returns></returns>
        internal bool AddToVisibleByWidth()
        {
            // Do viditelných položek dám položky, jejichž šířka je 0 a více:
            if (this.ElementRowArea.Width >= 0) return true;
            //   ... a trojúhelníky:
            if (this.DataElement.ElementShapeType == GraphElementShapeType.RightTriangle) return true;
            //   ... a elementy s obrázkem:
            if (this.DataElement.ElementImage != null) return true;
            //   a nic více:
            return false;
        }
        #endregion
        #region Detektory stavu (property typu Is?xxx { get; })
        /// <summary>
        /// Element je možno selectovat?
        /// </summary>
        internal bool IsSelectable { get { return ((this.DataElement.Properties & ElementProperties.Selectable) > 0); } }
        /// <summary>
        /// { get; } Obsahuje true, pokud tento objekt je prázdný. Svou prázdnotu odvozuje od this.DataElement.GId.IsEmpty.
        /// </summary>
        internal bool IsEmpty { get { return this.DataElement.GId.IsEmpty; } }
        /// <summary>
        /// Příznak, že jde o Temporary prvek
        /// </summary>
        internal bool IsTemporary { get { return (this.DataElement.ElementLayer == GraphLayerType.ElementTempLayer); } }
        /// <summary>
        /// Příznak, že jde o prvek na vrstvě SubLayer
        /// </summary>
        internal bool IsSubLayer { get { return (this.DataElement.ElementLayer == GraphLayerType.ElementSubLayer); } }
        /// <summary>
        /// Příznak, že jde o prvek na vrstvě ItemLayer
        /// </summary>
        internal bool IsItemLayer { get { return (this.DataElement.ElementLayer == GraphLayerType.ElementItemLayer); } }
        #endregion
        #region Komparátory (třídičky)
        /// <summary>
        /// Komparátor podle vizuálních priorit.
        /// Nejprve dává objekty "dospodu", a v rámci vrstvy je pak třídění podle času Begin
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByVisualLayer(GridDataElementCls a, GridDataElementCls b)
        {
            int cl = ((int)a.DataElement.ElementLayer).CompareTo((int)b.DataElement.ElementLayer);
            if (cl != 0) return cl;

            int cb = a.DataElement.TimeRange.Begin.CompareTo(b.DataElement.TimeRange.Begin);
            if (cb != 0) return cb;

            int ce = a.DataElement.GId.CompareTo(b.DataElement.GId);
            return ce;
        }
        /// <summary>
        /// Komparátor podle vrstvy (v pořadí: TempLayer, SubLayer, ItemLayer) a podle času počátku (TimeRange.Begin).
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByLayerBegin(GridDataElementCls a, GridDataElementCls b)
        {
            if (a.DataElement.ElementLayer != b.DataElement.ElementLayer)
                // Objekty na různých vrstvách: nižší vrstva bude dříve:
                return ((int)a.DataElement.ElementLayer).CompareTo((int)b.DataElement.ElementLayer);

            TimeRange at = a.DataElement.TimeRange;
            TimeRange bt = b.DataElement.TimeRange;
            // Objekty na stejné vrstvě: objekt s nižším Begin bude dříve:
            int cmp = at.Begin.CompareTo(bt.Begin);
            if (cmp != 0) return cmp;

			// Pokud mají objekty shodný čas Begin, použijeme k porovnání hodnotu SubTimeSort:
			cmp = a.DataElement.SubTimeSort.CompareTo(b.DataElement.SubTimeSort);
			if (cmp != 0) return cmp;
            
			// Pokud je stejné Begin, pak dřív bude element s VYŠŠÍM End:
            return bt.End.CompareTo(at.End);
        }
        /// <summary>
        /// Komparátor podle času počátku. Neřeší vrstvu ElementLayer.
		/// Řadí nejprve elementy s nižším Begin, v rámci shodného Begin pak řadí nejprve elementy s nulovou délkou a pak kladné,
		/// dále pak podle SubTimeSort, a konečně přihlíží i k End (nejprve řadí ty s vyšším End).
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByBegin(GridDataElementCls a, GridDataElementCls b)
        {
            TimeRange at = a.DataElement.TimeRange;
            TimeRange bt = b.DataElement.TimeRange;
            // Objekty na stejné vrstvě: objekt s nižším Begin bude dříve:
            int cmp = at.Begin.CompareTo(bt.Begin);
            if (cmp != 0) return cmp;

			// Pokud mají objekty shodný počátek, pak nejdříve musím zpracovat objekty s nulovou délkou, 
			//  protože za ně potom mohu umístit objekty se shodným Begin, ale s kladnou délkou:
			// Z jiného pohledu: na objekty s nulovou délkou nahlížím jako by začínaly dříve, než objekty s nenulovou délkou.
			// Vychází to z principu zpracování pomocí času SubTimeRange, kdy se objekty s kladnou délkou o 0,3 milisec zkrátí z každé strany (Begin i End),
			//  a elementy s nulovou délkou se o tyto 0,3 milisec zvětší (vejdou se tak mezi dva elementy nenulové, ale dva nulové elementy se zobrazují nad sebou):
			if (at.Seconds == 0D && bt.Seconds > 0D) return -1;         // "a" je nulové a "b" není, "a" půjde dříve.
			if (at.Seconds > 0D && bt.Seconds == 0D) return 1;          //   ... a naopak.

            // Pokud mají objekty shodný čas Begin, použijeme k porovnání hodnotu SubTimeSort:
            cmp = a.DataElement.SubTimeSort.CompareTo(b.DataElement.SubTimeSort);
            if (cmp != 0) return cmp;

            // Pokud mají stejný Begin, pak dříve (niže) bude element, který má vyšší End (abych nestavěl schody, ale hory):
            return bt.End.CompareTo(at.End);
        }
        /// <summary>
        /// Komparátor podle souřadnice levého dolního rohu.
        /// Nejprve dává objekty "dospodu", a v rámci vrstvy je pak třídění podle času Begin
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByPosition(GridDataElementCls a, GridDataElementCls b)
        {
            Point ap = a.ElementRelArea.Location;
            Point bp = b.ElementRelArea.Location;

            // Nejdříve elementy, které mají nejvyšší hodnotu ElementRelArea.Location.Y
            int cmp = bp.Y.CompareTo(ap.Y);
            if (cmp != 0) return cmp;

            // Pokud jsou na stejné výšce, pak nejdříve ty s nejnižší souřadnicí X:
            return ap.X.CompareTo(bp.X);
        }
        #endregion
    }
    #endregion
	#region CLASS GridDataVisualLineCls : pracovní dočasné podklady pro fyzické zobrazování dat (soupis jen viditelných řádků, vypočtené ikony, pozice řádků, výška, Rectangle)
	/// <summary>
	/// GridDataVisualLineCls : podklady pro fyzické zobrazování dat (soupis jen viditelných řádků, vypočtené ikony, pozice řádků, výška, Rectangle).
	/// Jeden záznam popisuje jeden viditelný řádek. Po zneviditelnění řádku (nadřízený node se zavře) tento objekt z paměti zmizí.
	/// Pokud je jeden fyzický řádek zobrazen ve více vizuálních řádcích, existuje více instancí této třídy GridDataVisualLineCls.
	/// Tato třída tedy popisuje konkrétní umístění dat řádku v prostoru grafu
	/// Po každém načtení dalších dat, po každém otevření / zavření nodu se znovu celý seznam Rows[] vypočítá.
	/// </summary>
	internal class GridDataVisualLineCls
	{
		#region Konstrukce, proměnné
        /// <summary>
        /// Plný konstruktor
        /// </summary>
        /// <param name="rowIdx">Index řádku</param>
        /// <param name="nodeId">ID nodu, který je zde zobrazen</param>
        /// <param name="dataRow">Data řádku</param>
        /// <param name="nodeTop">Top souřadnice</param>
        /// <param name="prevIcon">Předcházející ikony Tree</param>
        /// <param name="currIcon">Aktuální ikona Tree</param>
        /// <param name="addIcon">Přídavná ikona Tree (typicky TreeLineType.None, anebo TreeLineType.ExpandedParent)</param>
        internal GridDataVisualLineCls(int rowIdx, GridNodeRowCls node, GridDataRowCls dataRow, int nodeTop, List<TreeLineType> prevIcon, TreeLineType currIcon, TreeLineType addIcon)
        {
            this._RowIdx = rowIdx;
            this._Node = node;
            this._DataRow = dataRow;
            this._Top = nodeTop;
            this.IconTypes = new TreeLineType[prevIcon.Count + 1];
            prevIcon.CopyTo(this.IconTypes, 0);
            this.IconTypes[prevIcon.Count] = currIcon;
            this._IconAdded = addIcon;
        }
        /// <summary>
        /// Prázdný konstruktor
        /// </summary>
        /// <param name="currIcon"></param>
        internal GridDataVisualLineCls(TreeLineType currIcon)
        {
            this._RowIdx = -1;
            this._DataRow = null;
            this._Top = -1;
            this.IconTypes = new TreeLineType[0];
            this._IconAdded = TreeLineType.None;
        }
        private int _RowIdx;
		private GridDataRowCls _DataRow;
        private GridNodeRowCls _Node;
		private int _Top;
		private TreeLineType _IconAdded = TreeLineType.None;
		public override string ToString()
		{
			GridDataRowCls nodeData = this._DataRow;
			return "[" + this.RowIdx.ToString() + "] = " + this.RowId.ToString() + ": " + this.DataRow.RowItem.ToString() + "; Elements:" + this.DataRow.ElementCount;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Index řádku.
		/// Je jednoznačný jen v rámci jednoho vytvořeného seznamu Rows.
		/// Seznam Rows se vytváří po každém načtení dat, po každém otevření/zavření nodu a po každé změně filtrování.
		/// Seznam obsahuje řádky v nepřerušované řadě { 0 - max }, kde RowIdx odpovídá indexu v seznamu Rows.
		/// Jakkoliv nalezený řádek tedy v sobě nese informaci o své pozici, a umožní tak najít i své sousední řádky.
		/// </summary>
		internal int RowIdx { get { return this._RowIdx; } }
        /// <summary>
        /// Pracovní data z datové vrstvy grafu. Je to o jednu vrstvu výše než jsou data z DataSource.
        /// Tato data jsou permanentní, po načtení z DataSource se nemění.
        /// Nikdy není null (záznam this se vytváří pouze na základě not null podkladových dat).
        /// </summary>
        internal GridDataRowCls DataRow { get { return this._DataRow; } }
        /// <summary>
        /// Data o nodu, který je zde zobrazen.
        /// Tato data jsou permanentní, po načtení z DataSource se nemění.
        /// Nikdy není null (záznam this se vytváří pouze na základě not null podkladových dat).
        /// </summary>
        internal GridNodeRowCls Node { get { return this._Node; } }
		/// <summary>
		/// RowID řádku = kombinace GID a NodeId.
		/// </summary>
		internal RowID RowId { get { return new RowID(this._DataRow.GId, this._Node.Id); } }
		/// <summary>
		/// ID nodu. Jde o trvalý klíč nodu v mapě stromu. ID nodu je permanentní a unikátní.
		/// ID nodu je klíčem ke všem akcím na vizuální úrovni, pro vizuální práci nelze využívat GID 
		/// (to je z vizuálního hlediska nejednoznačné: jeden GID se může zobrazit na více řádcích, pro různé parenty).
		/// </summary>
        internal int NodeId { get { return this._Node.Id; } }
		/// <summary>
		/// GID dat v tomto řádku.
		/// Trvalá konstanta, odkazuje se na existující data v databázi.
		/// Nezmění se ani přenačtením dat, ani změnami seznamu.
		/// Ukazuje na logická data řádku, ale nelze používat k orientaci v mapě Tree, na to je třeba používat NodeId.
		/// </summary>
		internal GID DataGId { get { return this._DataRow.GId; } }
		/// <summary>
		/// Pozice Top tohoto řádku v absolutní souřadnici. První řádek (s indexem 0) má Top = 0, 
		/// všechny další řádky mají svůj Top = (Minulý řádek.Top + .Height).
		/// Jde o jednoznačnou pozici řádku ve virtuálním dlouhém seznamu, z něhož se zobrazuje jen určitý výsek.
		/// </summary>
		internal int Top { get { return this._Top; } set { this._Top = value; } }
		/// <summary>
		/// Výška tohoto řádku. Fyzicky je uložena v this.DataRow.RowHeightPx.
		/// Pozor: čtení této property se odkazuje na property HeightPx z podkladového datového řádku 
		/// a může tedy způsobit přepočet pozic elementů v podkladovém řádku.
		/// Nikoli načítání z datového zdroje.
		/// </summary>
		internal int HeightPx { get { return (this._DataRow != null ? this._DataRow.RowHeightPx : 0); } }
		/// <summary>
		/// Vypočítaná pozice Bottom tohoto řádku.
		/// Pozor: čtení této property se odkazuje na property HeightPx z podkladového datového řádku 
		/// a může tedy způsobit přepočet pozic elementů v podkladovém řádku.
		/// Nikoli načítání z datového zdroje.
		/// Jde o pozici pixelu, na který de facto už se žádný objekt tohoto řádku nekreslí. Tak je to v grafice zvykem.
		/// </summary>
		internal int Bottom { get { return this._Top + this.HeightPx; } }
		/// <summary>
		/// Ikony Tree tohoto řádku. Odpovídají pozici řádku v Tree, a odpovídají otevření/zavření nodu.
		/// </summary>
		internal TreeLineType[] IconTypes;
		/// <summary>
		/// Ikona stromu, která se výjimečně kreslí na místě začátku nodu. Obsahuje buď TreeLineType.None, anebo TreeLineType.ExpandedParent.
		/// Hodnota TreeLineType.ExpandedParent je počáteční vodící linkou k prvnímu ChildNodu, protože u řádků s velkou Height by to bez ní vypadalo blbě.
		/// </summary>
		internal TreeLineType IconAdded { get { return this._IconAdded; } }

		#endregion
	}
	#endregion
	#region STRUCT OneDimensionI : struktura Begin, Length, End. Integer.
	/// <summary>
	/// OneDimensionI : struktura Begin, Length, End. Integer.
	/// </summary>
	internal struct OneDimensionI
	{
		public OneDimensionI(int begin, int length)
		{
			this._Begin = begin;
			this._Length = length;
		}
		private int _Begin;
		private int _Length;
		public override string ToString()
		{
			return "Begin=" + this.Begin + "; Length=" + this.Length;
		}
		/// <summary> { get; set; }
		/// Počáteční bod
		/// </summary>
		public int Begin { get { return _Begin; } set { _Begin = value; } }
		/// <summary> { get; set; }
		/// Délka
		/// </summary>
		public int Length { get { return _Length; } set { _Length = value; } }
		/// <summary> { get; }
		/// Koncový bod = Begin + Length
		/// </summary>
		public int End { get { return _Begin + _Length; } }
		/// <summary> { get; }
		/// Příznak prázdnosti dimenze (0,0)
		/// </summary>
		public bool IsEmpty { get { return (this._Begin == 0 && this._Length == 0); } }
		/// <summary> { get; }
		/// Obsahuje Empty prvek
		/// </summary>
		public static OneDimensionI Empty { get { return new OneDimensionI(0, 0); } }
	}
	#endregion
    #region enum GraphTempElementState
    /// <summary>
    /// Stavy temp elementu v průběho jeho života
    /// </summary>
    internal enum GraphTempElementState
    {
        None = 0,
        /// <summary>
        /// Běžný datový element, nejde o Temp element
        /// </summary>
        Standard,
        /// <summary>
        /// Temp element, zpracovává se
        /// </summary>
        TempWork,
        /// <summary>
        /// Temp element s chybou
        /// </summary>
        TempError
    }
    #endregion

}
